Newer
Older
Import / applications / MakePDF / Tests / ExiProcessor / src / ExiProcessor.java
/*
 * @(#)ExiProcessor.java
 *
 * Copyright (C) 2011 The MITRE Corporation
 *
 * This program and its interfaces are free software;
 * you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.NumberFormat;
import java.util.Locale;
import java.util.Map;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

import com.siemens.ct.exi.CodingMode;
import com.siemens.ct.exi.EXIFactory;
import com.siemens.ct.exi.FidelityOptions;
import com.siemens.ct.exi.GrammarFactory;
import com.siemens.ct.exi.SchemaIdResolver;
import com.siemens.ct.exi.api.sax.EXIResult;
import com.siemens.ct.exi.api.sax.EXISource;
import com.siemens.ct.exi.exceptions.EXIException;
import com.siemens.ct.exi.exceptions.UnsupportedOption;
import com.siemens.ct.exi.grammar.Grammar;
import com.siemens.ct.exi.helpers.DefaultEXIFactory;

/** 
 * The ExiProcessor class defines static methods (including main())
 * to demonstrate the use of EXI encoding and decoding
 * using the EXIficient library (http://exificient.sourceforge.net/).
 *
 * For more information on Efficient XML Interchange (XML), 
 * see http://www.w3.org/TR/2011/REC-exi-20110310/
 * 
 * @author Craig Garrett, MITRE, garrett@mitre.org
 */
public class ExiProcessor {

	private static NumberFormat nf = NumberFormat.getInstance(Locale.ENGLISH); // for pretty-printing file sizes
	private static final String COMMAND_LINE = "java -jar ExiProcessor.jar";

	public static boolean isEncoding(Map<ExiOption, String> commandLineArgs) {
		return commandLineArgs.containsKey(ExiOption.XML_IN) && commandLineArgs.containsKey(ExiOption.EXI_OUT) && !commandLineArgs.containsKey(ExiOption.XML_OUT) && !commandLineArgs.containsKey(ExiOption.EXI_IN);
	}
	
	public static void main(String[] args) throws IOException {
		
		Map<ExiOption, String> commandLineArgs = null;
		if (args.length < 2)
			printHelp(); // this calls System.exit(0)
		else
			commandLineArgs = parseCommandLineArgs(args); // this calls System.exit(0) if there is an error
		
		try {
			if (isEncoding(commandLineArgs)) {
				// read in XML and write out EXI to a file
				File inputFile = new File(commandLineArgs.get(ExiOption.XML_IN));
				long inputSize = inputFile.length();
				System.out.println("Reading XML: " + commandLineArgs.get(ExiOption.XML_IN) + "\n");
				util.BufferedOutputStream os = new util.BufferedOutputStream(new FileOutputStream(commandLineArgs.get(ExiOption.EXI_OUT)));
				EXIFactory exiFactory = createEXIFactory(commandLineArgs);
				System.out.println("Writing EXI: " + commandLineArgs.get(ExiOption.EXI_OUT) + "\n");
				xmlToExi(commandLineArgs, inputFile, os, exiFactory);
				long outputSize = os.getCount();
				os.close();
				double percentageSaved = ExiProcessor.round((1.0 - ((double) outputSize) / ((double) inputSize)) * 100.0, 1);
				double ratio = ExiProcessor.round((double) inputSize / (double) outputSize, 1);
				System.out.println("EXI encoding reduced the XML from " + nf.format(inputSize) + " bytes to " + nf.format(outputSize) + " bytes");
				System.out.println("Space Savings: " + percentageSaved + "%, Compression Ratio: " + ratio);
			}
			else if (commandLineArgs.containsKey(ExiOption.EXI_IN) && commandLineArgs.containsKey(ExiOption.XML_OUT) && !commandLineArgs.containsKey(ExiOption.EXI_OUT) && !commandLineArgs.containsKey(ExiOption.XML_IN)) {
				// read in EXI and write out XML to a file
				System.out.println("Reading EXI: " + commandLineArgs.get(ExiOption.EXI_IN));
				util.BufferedOutputStream os = new util.BufferedOutputStream(new FileOutputStream(commandLineArgs.get(ExiOption.XML_OUT)));
				EXIFactory exiFactory = createEXIFactory(commandLineArgs);
				System.out.println("Writing XML: " + commandLineArgs.get(ExiOption.XML_OUT) + "\n");
				exiToXml(commandLineArgs, os, exiFactory);
				System.out.println("Finished writing " + nf.format(os.getCount()) + " bytes of XML to " + commandLineArgs.get(ExiOption.XML_OUT));
				os.close();
			}
			else if (commandLineArgs.containsKey(ExiOption.EXI_IN) && !commandLineArgs.containsKey(ExiOption.XML_OUT) && !commandLineArgs.containsKey(ExiOption.EXI_OUT) && !commandLineArgs.containsKey(ExiOption.XML_IN)) {
				// read in EXI and print XML to stdout
				System.out.println("Reading EXI: " + commandLineArgs.get(ExiOption.EXI_IN));
				System.out.println();
				util.BufferedOutputStream os = new util.BufferedOutputStream(System.out);
				EXIFactory exiFactory = createEXIFactory(commandLineArgs);
				System.out.println("Resulting XML:\n");
				exiToXml(commandLineArgs, os, exiFactory);
				os.realFlush();
				System.out.println("\n\n(" + nf.format(os.getCount()) + " bytes)");
			}
			else {
				printBriefHelp(); // this calls System.exit(0)
			}
		} catch (FileNotFoundException e) {
			System.err.println("ERROR: FileNotFoundException: " + e.getMessage());
			System.exit(1);
		}
	}
	
    /**
     * Reads a text XML file from disk, encodes it to EXI, and writes the EXI to an output stream.
     * 
     * @param commandLineArgs a map of ExiOptions and values to be used in encoding the XML into EXI
     * @param inputFile the file containing the source XML (in normal text form)
     * @param outputStream the destination of the EXI
     * @param exiFactory the EXIficient object containing all of the EXI Options and configurations to be used when encoding the XML into EXI
     */
	public static void xmlToExi(Map<ExiOption, String> commandLineArgs, File inputFile, OutputStream outputStream, EXIFactory exiFactory) {
		try {
			EXIResult exiResult = new EXIResult(exiFactory);
			exiResult.setOutputStream(outputStream);
			XMLReader xmlReader = XMLReaderFactory.createXMLReader();
			xmlReader.setContentHandler(exiResult.getHandler());
			xmlReader.setProperty("http://xml.org/sax/properties/lexical-handler", exiResult.getLexicalHandler()); // set LexicalHandler - see http://xerces.apache.org/xerces2-j/properties.html
			xmlReader.setProperty("http://xml.org/sax/properties/declaration-handler", exiResult.getLexicalHandler()); // set DeclHandler - see http://xerces.apache.org/xerces2-j/properties.html
			xmlReader.parse(new InputSource(new FileInputStream(inputFile)));
		} catch (FileNotFoundException e) {
			System.err.println("ERROR: FileNotFoundException: " + e.getMessage());
			e.printStackTrace();
			System.exit(1);
		} catch (IOException e) {
			System.err.println("ERROR: IOException: " + e.getMessage());
			e.printStackTrace();
			System.exit(1);
		} catch (EXIException e) {
			System.err.println("ERROR: EXIException: " + e.getMessage());
			e.printStackTrace();
			System.exit(1);
		} catch (SAXException e) {
			System.err.println("ERROR: SAXException: " + e.getMessage());
			e.printStackTrace();
			System.exit(1);
		}
	}
	
    /**
     * Reads a binary EXI file from disk, decodes it to text XML, and writes the XML to an output stream.
     * 
     * @param commandLineArgs a map of ExiOptions and values to be used in decoding the EXI into XML
     * @param outputStream the destination of the EXI
     * @param exiFactory the EXIficient object containing all of the EXI Options and configurations to be used when encoding the XML into EXI
     */
	public static void exiToXml(Map<ExiOption, String> commandLineArgs, OutputStream outputStream, EXIFactory exiFactory) {
		try {
			EXISource exiSource = new EXISource(exiFactory);
			XMLReader xmlReader = exiSource.getXMLReader();
			TransformerFactory transformerFactory = TransformerFactory.newInstance();
			Transformer transformer = transformerFactory.newTransformer();
			InputStream inputStream = new FileInputStream(commandLineArgs.get(ExiOption.EXI_IN));
			SAXSource saxSource = new SAXSource(new InputSource(inputStream));
			saxSource.setXMLReader(xmlReader);
			//transformer.setOutputProperty(OutputKeys.METHOD, "xml");
			//transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "");
			//System.out.println("transformer.getOutputProperties().toString() = " + transformer.getOutputProperties().toString());
			transformer.transform(saxSource, new StreamResult(outputStream));
		} catch (EXIException e) {
			System.err.println("ERROR: EXIException: " + e.getMessage());
			e.printStackTrace();
			System.exit(1);
		} catch (TransformerConfigurationException e) {
			System.err.println("ERROR: TransformerConfigurationException: " + e.getMessage());
			e.printStackTrace();
			System.exit(1);
		} catch (FileNotFoundException e) {
			System.err.println("ERROR: FileNotFoundException: " + e.getMessage());
			e.printStackTrace();
			System.exit(1);
		} catch (TransformerException e) {
			System.err.println("ERROR: TransformerException: " + e.getMessage());
			e.printStackTrace();
			System.exit(1);
		}
	}
	
    /**
     * This method uses the given command line arguments to 
     * construct and configure a com.siemens.ct.exi.EXIFactory 
     * object which will be used to either encode or decode EXI.
     * 
     * @param commandLineArgs a map of ExiOption enum values and String values (not all ExiOption enums require a String value)
     */
	private static com.siemens.ct.exi.EXIFactory createEXIFactory(Map<ExiOption, String> commandLineArgs) {
		System.out.println("\tUsing the following EXI Options:");

		EXIFactory exiFactory = DefaultEXIFactory.newInstance(); // construct a basic com.siemens.ct.exi.EXIFactory
		
		boolean headerOptionsAutoEnabledBecauseOfSchemaID = false;
		if (commandLineArgs.containsKey(ExiOption.SCHEMAID) && !commandLineArgs.containsKey(ExiOption.HEADER_OPTIONS)) {
			commandLineArgs.put(ExiOption.HEADER_OPTIONS, null);
			headerOptionsAutoEnabledBecauseOfSchemaID = true;
		}
		
		try {
			
			com.siemens.ct.exi.EncodingOptions encodingOptions = com.siemens.ct.exi.EncodingOptions.createDefault();
			// COOKIE
			if (commandLineArgs.containsKey(ExiOption.COOKIE)) {
				printEnabledMessage(ExiOption.COOKIE);
				encodingOptions.setOption(com.siemens.ct.exi.EncodingOptions.INCLUDE_COOKIE);
			}
			// HEADER_OPTIONS
			if (commandLineArgs.containsKey(ExiOption.HEADER_OPTIONS)) {
				printEnabledMessage(ExiOption.HEADER_OPTIONS, headerOptionsAutoEnabledBecauseOfSchemaID ? " (auto-enabled due to -" + ExiOption.SCHEMAID.getCommandLineArg() + " option)" : "");
				encodingOptions.setOption(com.siemens.ct.exi.EncodingOptions.INCLUDE_OPTIONS);
			}
			// SCHEMAID
			// see below in grammar section
			
			// XSI_SCHEMALOCATION
			if (commandLineArgs.containsKey(ExiOption.XSI_SCHEMALOCATION)) {
				printEnabledMessage(ExiOption.XSI_SCHEMALOCATION);
				encodingOptions.setOption(com.siemens.ct.exi.EncodingOptions.INCLUDE_XSI_SCHEMALOCATION);
			}
			exiFactory.setEncodingOptions(encodingOptions);
			
			FidelityOptions fidelityOptions = FidelityOptions.createDefault();
			
			// STRICT
			if (commandLineArgs.containsKey(ExiOption.STRICT)) {
				printEnabledMessage(ExiOption.STRICT);
				fidelityOptions.setFidelity(FidelityOptions.FEATURE_STRICT, true);
			}
			else
				fidelityOptions.setFidelity(FidelityOptions.FEATURE_STRICT, false);
			
			// PRESERVE_NAMESPACE_PREFIXES
			if (commandLineArgs.containsKey(ExiOption.PRESERVE_NAMESPACE_PREFIXES)) {
				printEnabledMessage(ExiOption.PRESERVE_NAMESPACE_PREFIXES);
				fidelityOptions.setFidelity(FidelityOptions.FEATURE_PREFIX, true);
			}
			else
				fidelityOptions.setFidelity(FidelityOptions.FEATURE_PREFIX, false);
			
			// PRESERVE_DTD
			if (commandLineArgs.containsKey(ExiOption.PRESERVE_DTD)) {
				printEnabledMessage(ExiOption.PRESERVE_DTD);
				fidelityOptions.setFidelity(FidelityOptions.FEATURE_DTD, true);
			}
			else
				fidelityOptions.setFidelity(FidelityOptions.FEATURE_DTD, false);
			
			// PRESERVE_LEXICAL_VALUES
			if (commandLineArgs.containsKey(ExiOption.PRESERVE_LEXICAL_VALUES)) {
				printEnabledMessage(ExiOption.PRESERVE_LEXICAL_VALUES);
				fidelityOptions.setFidelity(FidelityOptions.FEATURE_LEXICAL_VALUE, true);
			}
			else
				fidelityOptions.setFidelity(FidelityOptions.FEATURE_LEXICAL_VALUE, false);
			
			// PRESERVE_COMMENTS
			if (commandLineArgs.containsKey(ExiOption.PRESERVE_COMMENTS)) {
				printEnabledMessage(ExiOption.PRESERVE_COMMENTS);
				fidelityOptions.setFidelity(FidelityOptions.FEATURE_COMMENT, true);
			}
			else
				fidelityOptions.setFidelity(FidelityOptions.FEATURE_COMMENT, false);
			
			// PRESERVE_PIS
			if (commandLineArgs.containsKey(ExiOption.PRESERVE_PI)) {
				printEnabledMessage(ExiOption.PRESERVE_PI);
				fidelityOptions.setFidelity(FidelityOptions.FEATURE_PI, true);
			}
			else
				fidelityOptions.setFidelity(FidelityOptions.FEATURE_PI, false);
			
			// SELF_CONTAINED
			if (commandLineArgs.containsKey(ExiOption.SELF_CONTAINED)) {
				printEnabledMessage(ExiOption.SELF_CONTAINED);
				fidelityOptions.setFidelity(FidelityOptions.FEATURE_SC, true);
			}
			else
				fidelityOptions.setFidelity(FidelityOptions.FEATURE_SC, false);
			
			exiFactory.setFidelityOptions(fidelityOptions); // tell the EXIFactory to use these FidelityOptions
			
			// CodingMode.COMPRESSION
			// CodingMode.BIT_PACKED is recommended for small messages, CodingMode.COMPRESSION is recommended for large messages
			if (commandLineArgs.containsKey(ExiOption.COMPRESSION)) {
				printEnabledMessage(ExiOption.COMPRESSION);
				exiFactory.setCodingMode(CodingMode.COMPRESSION);
			}
			else {
				// handle ALIGNMENT only when COMPRESSION is not specified
				if (commandLineArgs.containsKey(ExiOption.ALIGNMENT)) {
					CodingMode codingMode = null;
					try {
						codingMode = CodingMode.valueOf(commandLineArgs.get(ExiOption.ALIGNMENT).toUpperCase());
					} catch (Exception e) {
						System.out.println("\nError: the value '" + commandLineArgs.get(ExiOption.ALIGNMENT) + "' is not recognized. Permissible values are " + ExiOption.ALIGNMENT.getValuePlacemark());
						printHelp(); // this calls System.exit(0)
					}
					System.out.println("\t\t" + ExiOption.ALIGNMENT.getName() + " set to " + codingMode);
					exiFactory.setCodingMode(codingMode);
				}
			}

			// FRAGMENT
			if (commandLineArgs.containsKey(ExiOption.FRAGMENT)) {
				printEnabledMessage(ExiOption.FRAGMENT);
				exiFactory.setFragment(true);
			}

			// SCHEMA OPTIONS
			ExiProcessor.loadGrammar(exiFactory, commandLineArgs);
			
			// BLOCK_SIZE
			if (commandLineArgs.containsKey(ExiOption.BLOCK_SIZE)) {
				int blockSize = Integer.parseInt(commandLineArgs.get(ExiOption.BLOCK_SIZE)); 
				System.out.println("\t\t" + ExiOption.BLOCK_SIZE.getName() + " = " + blockSize + " bytes");
				exiFactory.setBlockSize(blockSize);
			}

			// VALUE_MAX_LENGTH
			if (commandLineArgs.containsKey(ExiOption.VALUE_MAX_LENGTH)) {
				int valueMaxLength = Integer.parseInt(commandLineArgs.get(ExiOption.VALUE_MAX_LENGTH)); 
				System.out.println("\t\t" + ExiOption.VALUE_MAX_LENGTH.getName() + " = " + valueMaxLength + " bytes");
				exiFactory.setValueMaxLength(valueMaxLength);
			}

			// VALUE_PARTITION_CAPACITY
			if (commandLineArgs.containsKey(ExiOption.VALUE_PARTITION_CAPACITY)) {
				int valuePartitionCapacity = Integer.parseInt(commandLineArgs.get(ExiOption.VALUE_PARTITION_CAPACITY)); 
				System.out.println("\t\t" + ExiOption.VALUE_PARTITION_CAPACITY.getName() + " = " + valuePartitionCapacity + " bytes");
				exiFactory.setValuePartitionCapacity(valuePartitionCapacity);
			}
			
			System.out.println();
			
		} catch (UnsupportedOption e) {
			System.err.println("ERROR: UnsupportedOption: " + e.getMessage());
			System.exit(1);
		}
		return exiFactory;
	}

	/** 
	 * The MySchemaIdResolver class implements the Grammar resolveSchemaId(String schemaId)
	 * method defined by the EXIficient SchemaIdResolver interface. This class
	 * assists in loading XML Schemas (i.e. creating EXI Grammars) via the 
	 * EXI Header->schemaId value and the -schema [XML Schema filename] 
	 * command line value.
	 */
	public static class MySchemaIdResolver implements SchemaIdResolver {
		Map<ExiOption, String> commandLineArgs;
		GrammarFactory grammarFactory;
		
		public MySchemaIdResolver(Map<ExiOption, String> commandLineArgs, GrammarFactory grammarFactory) {
			this.commandLineArgs = commandLineArgs;
			this.grammarFactory = grammarFactory;
		}
		
		@Override
	    /**
	     * Creates or loads an EXI Grammar based on the value of schemaId. 
	     * In EXI processing, this could involve a lookup table or other 
	     * index to find the correct XML schema, or it could simply use 
	     * the value as a filename and attempt to load the file. In this 
	     * case, the method first attempts to find a file at a location
	     * specified by commandLineArgs.get(ExiOption.SCHEMA) (in other 
	     * words, the value specified by the -schema option on the command
	     * line). If no such file exists, then the schemaId value is used
	     * as a file path/name, and the method attempts to load the grammar
	     * from that location. If neither string value points to an existing
	     * file, then an error message is printed to System.err and 
	     * System.exit(0) is called.
	     * 
	     * @param schemaId the schemaId value from the EXI Header
	     */
		public Grammar resolveSchemaId(String schemaId) throws EXIException {
			return resolveSchemaId(schemaId, false);
		}
		
	    /**
	     * Creates or loads an EXI Grammar based on the value of schemaId. 
	     * In EXI processing, this could involve a lookup table or other 
	     * index to find the correct XML schema, or it could simply use 
	     * the value as a filename and attempt to load the file. In this 
	     * case, the method first attempts to find a file at a location
	     * specified by commandLineArgs.get(ExiOption.SCHEMA) (in other 
	     * words, the value specified by the -schema option on the command
	     * line). If no such file exists, then the schemaId value is used
	     * as a file path/name, and the method attempts to load the grammar
	     * from that location. If neither string value points to an existing
	     * file, then an error message is printed to System.err and 
	     * System.exit(0) is called.
	     * 
	     * @param schemaId the schemaId value from the EXI Header
	     * @param manualCall if true, indicates that this method was called directly by code outside of EXIficient, if false, then this method was called by EXIficient as part of EXI processing
	     */
		public Grammar resolveSchemaId(String schemaId, boolean manualCall) throws EXIException {
			Grammar grammar = null;
			
			String explicitSchemaName = commandLineArgs.get(ExiOption.SCHEMA);
			File schemaFile = null;
			if (explicitSchemaName != null && !explicitSchemaName.isEmpty()) {
				schemaFile = new File(explicitSchemaName);
				if (!schemaFile.exists() && manualCall && !isEncoding(commandLineArgs)) {
					System.out.println("Warning: file given by -" + ExiOption.SCHEMA.getCommandLineArg() + " = '" + explicitSchemaName + "' not found. If the EXI Header contains a schemaId, this program will attempt to use that value.");
				}
			}
			if ((schemaFile == null || !schemaFile.exists()) && schemaId != null && !schemaId.isEmpty()) {
				schemaFile = new File(schemaId);
				if (schemaFile.exists()) {
					System.out.println("EXI Header->SchemaId = '" + schemaId + "'\n");
					//inputStream = new FileInputStream(schemaFile);
				}
				else if (!manualCall && !isEncoding(commandLineArgs)) {
					System.out.println("Warning: file given by EXI Header->SchemaId = '" + schemaId + "' not found.");
				}
			}
			if (schemaFile.exists()) {
				System.out.println("Loading schema: " + schemaFile.getAbsolutePath());
				grammar = grammarFactory.createGrammar(schemaFile.getAbsolutePath());
			}
			else {
				if (!manualCall && !isEncoding(commandLineArgs)) {
					System.err.println("ERROR: No Schema Found. -" + ExiOption.SCHEMA.getCommandLineArg() + " = " + explicitSchemaName + ", EXI Header->schemaId = '" + schemaId + "'");
					System.exit(0);
				}
			}
			return grammar;
		}
	}
	
    /**
     * This method loads a EXI Grammar object and provides it to the given EXIFactory.
     * 
     * @param exiFactory the EXIFactory to be modified (i.e. given the new Grammar)
     * @param commandLineArgs a map of ExiOption enum values and String values (not all ExiOption enums require a String value)
     */
	public static void loadGrammar(EXIFactory exiFactory, Map<ExiOption, String> commandLineArgs) {
		GrammarFactory grammarFactory = GrammarFactory.newInstance();
		Grammar grammar = null;
		MySchemaIdResolver schemaIdResolver = new MySchemaIdResolver(commandLineArgs, grammarFactory);
		exiFactory.setSchemaIdResolver(schemaIdResolver);
		try {
			if (commandLineArgs.containsKey(ExiOption.SCHEMA)) {
				printEnabledMessage(ExiOption.SCHEMA);
				//System.out.println("\t\t\tloading " + commandLineArgs.get(ExiOption.SCHEMA)); // a print statement is given in schemaIdResolver.resolveSchemaId
				grammar = schemaIdResolver.resolveSchemaId(null, true); // commandLineArgs.get(ExiOption.SCHEMA) was already passed to schemaIdResolver
				// SCHEMAID - only valid for encoding if ExiOption.SCHEMA is enabled
				if (commandLineArgs.containsKey(ExiOption.SCHEMAID)) {
					printEnabledMessage(ExiOption.SCHEMAID);
					exiFactory.getEncodingOptions().setOption(com.siemens.ct.exi.EncodingOptions.INCLUDE_SCHEMA_ID);
					System.out.println("\t\t\tschemaId set to '" + commandLineArgs.get(ExiOption.SCHEMAID) + "'");
					grammar.setSchemaId(commandLineArgs.get(ExiOption.SCHEMAID));
				}
				else {
					if (commandLineArgs.containsKey(ExiOption.HEADER_OPTIONS) && isEncoding(commandLineArgs)) {
						System.out.println("Warning: When using schema-informed encoding, enabling -" + ExiOption.HEADER_OPTIONS.getCommandLineArg() + " without also enabling -" + ExiOption.SCHEMAID.getCommandLineArg() + " will require the use of -" + ExiOption.SCHEMA.getCommandLineArg() + " " + ExiOption.SCHEMA.getValuePlacemark() + " when decoding.");
					}
				}
			}
			else if (commandLineArgs.containsKey(ExiOption.XSD_TYPES_ONLY)) {
				printEnabledMessage(ExiOption.XSD_TYPES_ONLY);
				grammar = grammarFactory.createXSDTypesOnlyGrammar();
			}
			else {
				if (isEncoding(commandLineArgs)) 
					System.out.println("\t\tSchemaless encoding will be used.");
				else
					System.out.println("\t\tSchemaless decoding will be used unless the EXI Header contains a schemaID value.");
				grammar = grammarFactory.createSchemaLessGrammar();
			}
			if (grammar != null)
				exiFactory.setGrammar(grammar);
		} catch (EXIException e) {
			e.printStackTrace();
		}
	}
	
    /**
     * A small helper method to print an 'enabled' message to stdout.
     * 
     * @param exiOption the ExiOption to print
     */
	private static void printEnabledMessage(ExiOption exiOption) {
		printEnabledMessage(exiOption, "");
	}
	
    /**
     * A small helper method to print an 'enabled' message to stdout.
     * 
     * @param exiOption the ExiOption to print
     * @param additionalText additional text to include after the enabled message - typically begin this String with a space or comma.
     */
	private static void printEnabledMessage(ExiOption exiOption, String additionalText) {
		System.out.println("\t\t" + exiOption.getName() + " enabled" + additionalText);
	}
	
    /**
     * Parses the command line arguments for ExiOptions and values. If an error
     * is detected, a short version of command line help is printed and System.exit(0)
     * is called.
     * 
     * @return a map of ExiOption enums and String values
     */
	private static Map<ExiOption, String> parseCommandLineArgs(String[] args) {
		Map<ExiOption, String> commandLineArgs = new java.util.EnumMap<ExiOption, String>(ExiOption.class);
		
		int i = 0;
		while (i < args.length) {
			ExiOption exiOption = ExiOption.valueOfCommandLineArg(args[i].replaceFirst("-", ""));
			if (exiOption != null) {
				if (i + 1 < args.length && !exiOption.getValuePlacemark().isEmpty()) {
					commandLineArgs.put(exiOption, args[i + 1]);
					i = i + 2;
				}
				else if (!(i + 1 < args.length) && !exiOption.getValuePlacemark().isEmpty()) {
					System.out.println("\nError: option " + args[i] + " requires a value");
					printBriefHelp(); // this calls System.exit(0)
				}
				else {
					commandLineArgs.put(exiOption, null);
					i++;
				}
			}
			else {
				System.out.println();
				System.out.println("ERROR: command line option '" + args[i] + "' not known");
				printBriefHelp(); // this calls System.exit(0)
			}
		}

		System.out.println();
		return commandLineArgs;
	}

    /**
     * Prints command-line help to stdout and then calls System.exit(0)
     */
	private static void printHelp() {
		System.out.println();
		String tab = "\t";
		System.out.println("Efficient XML Interchange (EXI) Processor");
		System.out.println("This program can be used to encode text XML to binary EXI, and decode EXI to text XML.");
		System.out.println();
		System.out.println("Encode XML to EXI:");
		System.out.println(tab + COMMAND_LINE + " -" + ExiOption.XML_IN.getCommandLineArg() + " sample.xml -" + ExiOption.EXI_OUT.getCommandLineArg() + " sample.exi");
		System.out.println();
		System.out.println("Decode EXI to XML:");
		System.out.println(tab + COMMAND_LINE + " -" + ExiOption.EXI_IN.getCommandLineArg() + " sample.exi -" + ExiOption.XML_OUT.getCommandLineArg() + " sample.xml");
		System.out.println();
		System.out.println("Decode EXI to stdout:");
		System.out.println(tab + COMMAND_LINE + " -" + ExiOption.EXI_IN.getCommandLineArg() + " sample.exi");
		System.out.println();
		System.out.println("Command Line Options:");
		System.out.println();
		for (ExiOption exiOption : ExiOption.values()) {
			System.out.println(tab + "-" + exiOption.getCommandLineArg() + " " + exiOption.getValuePlacemark());
			System.out.println(tab + tab + exiOption.getDescription());
			if (exiOption.getUrl().length() > 0)
				System.out.println(tab + tab + "URL: " + exiOption.getUrl());
			if (exiOption.getValuePlacemark().length() > 0)
				System.out.println(tab + tab + "Example: " + exiOption.getExample());
			System.out.println();
		}
		System.exit(0);
	}
	
    /**
     * Prints a short version of command-line help to stdout and then calls System.exit(0)
     */
	private static void printBriefHelp() {
		System.out.println();
		String tab = "\t";
		System.out.println("Efficient XML Interchange (EXI) Processor");
		System.out.println("This program can be used to encode text XML to binary EXI, and decode EXI to text XML.");
		System.out.println();
		System.out.println("For a full list of options, run:");
		System.out.println(tab + COMMAND_LINE);
		System.exit(0);
	}
	
    /**
     * Rounds a floating-point number to arbitrary precision.
     * 
     * @param d the floating-point number to round
     * @param n the number of decimal places to the right of the decimal point to leave. Negative values will round to the nearest 10, 100, etc.
     */
	private static double round(double d, int n){
		double factor = Math.pow(10, n);
		return Math.round(d * factor) / factor;
	}

}