/*
* Copyright (C) 2010 Brockmann Consult GmbH (info@brockmann-consult.de)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at your option)
* any later version.
* 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, see http://www.gnu.org/licenses/
*/
package com.bc.ceres.binio.binx;
import com.bc.ceres.binio.CompoundMember;
import com.bc.ceres.binio.CompoundType;
import com.bc.ceres.binio.DataFormat;
import com.bc.ceres.binio.SequenceType;
import com.bc.ceres.binio.SimpleType;
import com.bc.ceres.binio.Type;
import com.bc.ceres.core.Assert;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.JDOMException;
import org.jdom2.Namespace;
import org.jdom2.input.SAXBuilder;
import java.io.IOException;
import java.net.URI;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import static com.bc.ceres.binio.TypeBuilder.*;
/**
* Utlity class used to read BinX schema files.
* See the <a href="http://www.edikt.org/binx/">BinX Project</a>.
* <p/>
* This class is not thread-safe.
*/
public class BinX {
static final String ANONYMOUS_COMPOUND_PREFIX = "AnonymousCompound@";
static final String ARRAY_VARIABLE_PREFIX = "ArrayVariable@";
static final String DEFAULT_ELEMENT_COUNT_POSTFIX = "_Counter";
private final Map<String, String> parameters;
private final Map<String, Type> definitions;
private final Map<String, String> varNameMap;
private final Set<String> inlinedStructs;
private String elementCountPostfix;
private boolean singleDatasetStructInlined;
private boolean arrayVariableInlined;
private Map<String, SimpleType> primitiveTypes;
private Namespace namespace;
private static int anonymousCompoundId = 0;
public BinX() {
parameters = new HashMap<String, String>();
definitions = new HashMap<String, Type>();
varNameMap = new HashMap<String, String>();
inlinedStructs = new HashSet<String>();
primitiveTypes = new HashMap<String, SimpleType>();
primitiveTypes.put("byte-8", SimpleType.BYTE);
primitiveTypes.put("unsignedByte-8", SimpleType.UBYTE);
primitiveTypes.put("short-16", SimpleType.SHORT);
primitiveTypes.put("unsignedShort-16", SimpleType.USHORT);
primitiveTypes.put("integer-32", SimpleType.INT);
primitiveTypes.put("unsignedInteger-32", SimpleType.UINT);
primitiveTypes.put("long-64", SimpleType.LONG);
primitiveTypes.put("unsignedLong-64", SimpleType.ULONG);
primitiveTypes.put("float-32", SimpleType.FLOAT);
primitiveTypes.put("double-64", SimpleType.DOUBLE);
elementCountPostfix = DEFAULT_ELEMENT_COUNT_POSTFIX;
singleDatasetStructInlined = false;
arrayVariableInlined = false;
}
public String getParameter(String name) {
return parameters.get(name);
}
public String setParameter(String name, String value) {
if (value == null) {
return parameters.remove(name);
}
return parameters.put(name, value);
}
public Type getDefinition(String name) {
return definitions.get(name);
}
public Type setDefinition(String name, Type value) {
if (value == null) {
return definitions.remove(name);
}
return definitions.put(name, value);
}
public String setVarNameMapping(String sourceName, String targetName) {
if (targetName == null) {
return varNameMap.remove(sourceName);
}
return varNameMap.put(sourceName, targetName);
}
public void setVarNameMappings(Properties properties) {
if (properties != null) {
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
if (entry.getKey() instanceof String && entry.getValue() instanceof String) {
final String sourceName = (String) entry.getKey();
final String targetName = (String) entry.getValue();
setVarNameMapping(sourceName, targetName);
}
}
}
}
public boolean setTypeMembersInlined(String typeName, boolean b) {
if (!b) {
return inlinedStructs.remove(typeName);
}
return inlinedStructs.add(typeName);
}
public void setTypeMembersInlined(Properties properties) {
if (properties != null) {
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
if (entry.getKey() instanceof String) {
final String typeName = (String) entry.getKey();
setTypeMembersInlined(typeName, "true".equals(entry.getValue()));
}
}
}
}
public String getElementCountPostfix() {
return elementCountPostfix;
}
public void setElementCountPostfix(String elementCountPostfix) {
Assert.notNull(elementCountPostfix, "elementCountPostfix");
this.elementCountPostfix = elementCountPostfix;
}
public boolean isSingleDatasetStructInlined() {
return singleDatasetStructInlined;
}
public void setSingleDatasetStructInlined(boolean singleDatasetStructInlined) {
this.singleDatasetStructInlined = singleDatasetStructInlined;
}
public boolean isArrayVariableInlined() {
return arrayVariableInlined;
}
public void setArrayVariableInlined(boolean arrayVariableInlined) {
this.arrayVariableInlined = arrayVariableInlined;
}
public DataFormat readDataFormat(URI uri) throws BinXException, IOException {
return readDataFormat(uri, uri.toString());
}
public DataFormat readDataFormat(URI uri, String formatName) throws BinXException, IOException {
DataFormat format = new DataFormat(parseDocument(uri));
format.setName(formatName);
for (Map.Entry<String, Type> entry : definitions.entrySet()) {
format.addTypeDef(entry.getKey(), entry.getValue());
}
return format;
}
private CompoundType parseDocument(URI uri) throws IOException, BinXException {
SAXBuilder builder = new SAXBuilder();
Document document;
try {
document = builder.build(uri.toURL());
} catch (JDOMException e) {
throw new BinXException(MessageFormat.format("Failed to read ''{0}''", uri), e);
}
Element binxElement = document.getRootElement();
this.namespace = binxElement.getNamespace();
parseParameters(binxElement);
parseDefinitions(binxElement);
return parseDataset(binxElement);
}
private void parseParameters(Element binxElement) throws BinXException {
Element parametersElement = getChild(binxElement, "parameters", false);
if (parametersElement != null) {
// todo - implement parameters (nf - 2008-11-27)
throw new BinXException(
MessageFormat.format("Element ''{0}'': Not implemented", parametersElement.getName()));
}
}
private void parseDefinitions(Element binxElement) throws IOException, BinXException {
Element definitionsElement = getChild(binxElement, "definitions", false);
if (definitionsElement != null) {
List defineTypeElements = getChildren(definitionsElement, "defineType", false);
for (int i = 0; i < defineTypeElements.size(); i++) {
Element defineTypeElement = (Element) defineTypeElements.get(i);
String typeName = getTypeName(defineTypeElement, true);
Element child = getChild(defineTypeElement, true);
Type type = parseNonSimpleType(child);
if (type == null) {
throw new BinXException(MessageFormat.format("Element ''{0}'': ''{1}'' not expected here",
defineTypeElement.getName(), child.getName()));
}
if (definitions.containsKey(typeName)) {
throw new BinXException(MessageFormat.format("Element ''{0}'': Duplicate type definition ''{1}''",
definitionsElement.getName(), typeName));
}
if (type instanceof CompoundType && type.getName().startsWith(ANONYMOUS_COMPOUND_PREFIX)) {
type = COMPOUND(typeName, ((CompoundType) type).getMembers());
}
definitions.put(typeName, type);
}
}
}
private CompoundType parseDataset(Element binxElement) throws BinXException, IOException {
Element datasetElement = getChild(binxElement, "dataset", true);
CompoundType compoundType = parseStruct(datasetElement);
// inline single compound member
if (singleDatasetStructInlined
&& compoundType.getMemberCount() == 1
&& compoundType.getMember(0).getType() instanceof CompoundType) {
final CompoundMember member = compoundType.getMember(0);
return COMPOUND(member.getName(), ((CompoundType) member.getType()).getMembers());
} else {
return COMPOUND("Dataset", compoundType.getMembers());
}
}
private Type parseNonSimpleType(Element typeElement) throws BinXException {
String childName = typeElement.getName();
Type type = null;
if (childName.equals("struct")) {
type = parseStruct(typeElement);
} else if (childName.equals("union")) {
type = parseUnion(typeElement);
} else if (childName.equals("arrayFixed")) {
type = parseArrayFixed(typeElement);
} else if (childName.equals("arrayStreamed")) {
type = parseArrayStreamed(typeElement);
} else if (childName.equals("arrayVariable")) {
type = parseArrayVariable(typeElement);
}
return type;
}
private Type parseAnyType(Element typeElement) throws BinXException {
String typeName = typeElement.getName();
Type type;
if (typeName.equals("useType")) {
type = parseUseType(typeElement);
} else {
type = parseNonSimpleType(typeElement);
if (type == null) {
type = primitiveTypes.get(typeElement.getName());
if (type == null) {
throw new BinXException(MessageFormat.format("Element ''{0}'': Unknown type: {1}",
typeElement.getName(), typeName));
}
}
}
return type;
}
// <useType typeName="Confidence_Descriptors_Data_Type" varName="Confidence_Descriptors_Data"/>
//
private Type parseUseType(Element typeElement) throws BinXException {
String typeName = getTypeName(typeElement, true);
Type type = definitions.get(typeName);
if (type == null) {
throw new BinXException(MessageFormat.format("Element ''{0}'': Unknown type definition: {1}",
typeElement.getName(), typeName));
}
return type;
}
// <struct>
// <integer-32 varName="Days"/>
// <unsignedInteger-32 varName="Seconds"/>
// <unsignedInteger-32 varName="Microseconds"/>
// </struct>
//
private CompoundType parseStruct(Element typeElement) throws BinXException {
final List memberElements = getChildren(typeElement, false);
final ArrayList<CompoundMember> members = new ArrayList<CompoundMember>();
for (int i = 0; i < memberElements.size(); i++) {
final Element memberElement = (Element) memberElements.get(i);
final Type memberType = parseAnyType(memberElement);
if (memberType instanceof CompoundType) {
final CompoundType compoundType = (CompoundType) memberType;
// inline compound, if applicable
if (inlinedStructs.contains(memberType.getName())) {
for (final CompoundMember compoundMember : compoundType.getMembers()) {
members.add(MEMBER(compoundMember.getName(), compoundMember.getType()));
}
continue;
}
// inline variable-length array, if applicable
if (isArrayVariableInlined() && memberType.getName().startsWith(ARRAY_VARIABLE_PREFIX)) {
members.add(MEMBER(compoundType.getMemberName(0), compoundType.getMemberType(0)));
members.add(MEMBER(compoundType.getMemberName(1), compoundType.getMemberType(1)));
continue;
}
}
final String memberName = getVarName(memberElement, true);
members.add(MEMBER(memberName, memberType));
}
return COMPOUND(generateCompoundName(), members.toArray(new CompoundMember[members.size()]));
}
// <arrayVariable varName="SM_SWATH" byteOrder="littleEndian">
// <sizeRef>
// <unsignedInteger-32 varName="N_Grid_Points"/>
// </sizeRef>
// <useType typeName="Grid_Point_Data_Type"/>
// <dim/>
// </arrayVariable>
//
private CompoundType parseArrayVariable(Element typeElement) throws BinXException {
Element sizeRefElement = getChild(typeElement, "sizeRef", 0, true);
Element arrayTypeElement = getChild(typeElement, 1, true);
String sequenceName = getVarName(arrayTypeElement, false);
if (sequenceName == null) {
sequenceName = getVarName(typeElement, false);
if (sequenceName == null) {
throw new BinXException(MessageFormat.format("Element ''{0}'': Missing name", typeElement.getName()));
}
}
Element sizeRefTypeElement = getChild(sizeRefElement, true);
String sizeRefName = getVarName(sizeRefTypeElement, false);
if (sizeRefName == null) {
sizeRefName = sequenceName + elementCountPostfix;
}
Type sizeRefType = parseAnyType(sizeRefTypeElement);
if (!isIntegerType(sizeRefType)) {
throw new BinXException(
MessageFormat.format("Element ''{0}'': 'sizeRef' must be an integer type", typeElement.getName()));
}
Type arrayType = parseAnyType(arrayTypeElement);
SequenceType sequenceType = VAR_SEQUENCE(arrayType, sizeRefName);
return COMPOUND(generateArrayVariableCompoundName(sequenceName),
MEMBER(sizeRefName, sizeRefType),
MEMBER(sequenceName, sequenceType));
}
private Type parseUnion(Element typeElement) throws BinXException {
// todo - implement union (nf - 2008-11-27)
throw new BinXException(MessageFormat.format("Element ''{0}'': Type not implemented", typeElement.getName()));
}
// <arrayFixed varName="Radiometric_Accuracy">
// <float-32/>
// <dim indexTo="1"/>
// </arrayFixed>
private Type parseArrayFixed(Element typeElement) throws BinXException {
Element arrayTypeElement = getChild(typeElement, 0, true);
Element dimElement = getChild(typeElement, "dim", 1, true);
if (!dimElement.getChildren().isEmpty()) {
// todo - implement multi-dimensional arrays (rq - 2008-11-27)
throw new BinXException(
MessageFormat.format("Element ''{0}'': Multi-dimensional arrays not yet implemented",
typeElement.getName()));
}
final Type arrayType = parseAnyType(arrayTypeElement);
final int indexFrom = getAttributeIntValue(dimElement, "indexFrom", 0);
if (indexFrom != 0) {
throw new BinXException(
MessageFormat.format("Element ''{0}'': Attribute 'indexFrom' other than zero not supported.",
typeElement.getName()));
}
final int indexTo = getAttributeIntValue(dimElement, "indexTo");
return SEQUENCE(arrayType, indexTo + 1);
}
private Type parseArrayStreamed(Element typeElement) throws BinXException {
// todo - implement arrayStreamed (nf - 2008-11-27)
throw new BinXException(MessageFormat.format("Element ''{0}'': Type not implemented", typeElement.getName()));
}
private String getVarName(Element element, boolean require) throws BinXException {
final String name = getAttributeValue(element, "varName", require);
if (varNameMap.containsKey(name)) {
return varNameMap.get(name);
}
return name;
}
private static String getTypeName(Element element, boolean require) throws BinXException {
return getAttributeValue(element, "typeName", require);
}
private Element getChild(Element element, boolean require) throws BinXException {
return getChild(element, 0, require);
}
private Element getChild(Element element, int index, boolean require) throws BinXException {
return getChild(element, null, index, require);
}
private Element getChild(Element element, String name, boolean require) throws BinXException {
final Element child = element.getChild(name, namespace);
if (require && child == null) {
throw new BinXException(MessageFormat.format(
"Element ''{0}}': child ''{1}'' not found.", element.getName(), name));
}
return child;
}
private Element getChild(Element element, String name, int index, boolean require) throws BinXException {
final List children = getChildren(element, null, require);
if (children.size() <= index) {
if (require) {
if (name != null) {
throw new BinXException(MessageFormat.format(
"Element ''{0}'': Expected to have a child ''{1}'' at index {2}", element.getName(), name,
index));
} else {
throw new BinXException(MessageFormat.format(
"Element ''{0}'': Expected to have a child at index {1}", element.getName(), index));
}
} else {
return null;
}
}
final Element child = (Element) children.get(index);
if (name != null && !name.equals(child.getName())) {
throw new BinXException(MessageFormat.format(
"Element ''{0}'': Expected child ''{1}'' at index {2}", element.getName(), name, index));
}
return child;
}
private List getChildren(Element element, boolean require) throws BinXException {
return getChildren(element, null, require);
}
private List getChildren(Element element, String name, boolean require) throws BinXException {
final List children = element.getChildren(name, namespace);
if (require && children.isEmpty()) {
if (name != null) {
throw new BinXException(MessageFormat.format(
"Element ''{0}'': Expected to have at least one child of ''{1}''", element.getName(), name));
} else {
throw new BinXException(MessageFormat.format(
"Element ''{0}'': Expected to have at least one child", element.getName()));
}
}
return children;
}
private static String getAttributeValue(Element element, String name, boolean require) throws BinXException {
final String value = element.getAttributeValue(name);
if (require && value == null) {
throw new BinXException(MessageFormat.format(
"Element ''{0}'': attribute ''{1}'' not found.", element.getName(), name));
}
return value != null ? value.trim() : value;
}
private static int getAttributeIntValue(Element element, String attributeName) throws BinXException {
return getAttributeIntValue(element, attributeName, true);
}
private static int getAttributeIntValue(Element element, String attributeName, int defaultValue) throws BinXException {
Integer value = getAttributeIntValue(element, attributeName, false);
if (value != null) {
return value;
}
return defaultValue;
}
private static Integer getAttributeIntValue(Element element, String attributeName, boolean required) throws BinXException {
final String value = getAttributeValue(element, attributeName, required);
if (value == null) {
return null;
}
try {
return Integer.valueOf(value);
} catch (NumberFormatException e) {
throw new BinXException(MessageFormat.format("Element ''{0}'': Attribute ''{1}'' must be an integer.",
element.getName(),
attributeName));
}
}
private static String generateCompoundName() {
return ANONYMOUS_COMPOUND_PREFIX + anonymousCompoundId++;
}
private static String generateArrayVariableCompoundName(String sequenceName) {
return ARRAY_VARIABLE_PREFIX + sequenceName;
}
private static boolean isIntegerType(Type sizeRefType) {
return sizeRefType == SimpleType.BYTE
|| sizeRefType == SimpleType.UBYTE
|| sizeRefType == SimpleType.SHORT
|| sizeRefType == SimpleType.USHORT
|| sizeRefType == SimpleType.INT
|| sizeRefType == SimpleType.UINT
|| sizeRefType == SimpleType.LONG
|| sizeRefType == SimpleType.ULONG;
}
}