us.ihmc.scs2.session.mcap.MCAPSchema Maven / Gradle / Ivy
package us.ihmc.scs2.session.mcap;
import us.ihmc.euclid.tools.EuclidCoreIOTools;
import us.ihmc.scs2.session.mcap.encoding.CDRDeserializer;
import us.ihmc.scs2.session.mcap.specs.records.Message;
import us.ihmc.scs2.session.mcap.specs.records.Schema;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* Interface used to represent a Java interpreter of a MCAP schema.
*/
public class MCAPSchema
{
private final int id;
private final String name;
private final boolean isEnum;
private final String[] enumConstants;
private final List staticFields = new ArrayList<>();
private final List fields;
private final Map subSchemaMap;
protected MCAPSchema(String name, int id)
{
this(name, id, new ArrayList<>(), null);
}
protected MCAPSchema(String name, int id, List fields)
{
this(name, id, fields, null);
}
/**
* Creates a schema that represents a struct.
*
* @param name the name of the schema.
* @param id the ID of the schema.
* @param fields the fields of the schema.
* @param subSchemaMap the sub-schemas of the schema.
*/
protected MCAPSchema(String name, int id, List fields, Map subSchemaMap)
{
this.name = name;
this.id = id;
this.subSchemaMap = subSchemaMap;
this.fields = fields;
this.isEnum = false;
this.enumConstants = null;
}
/**
* Creates a schema that represents an enum.
*
* @param name the name of the schema.
* @param id the ID of the schema.
* @param enumConstants the enum constants of the schema.
*/
protected MCAPSchema(String name, int id, String[] enumConstants)
{
this.name = name;
this.id = id;
this.subSchemaMap = null;
this.fields = null;
this.isEnum = true;
this.enumConstants = enumConstants;
}
/**
* The ID of the schema as defined in the MCAP schema file, {@link Schema#id()}.
*
* @return the ID of the schema.
*/
public int getId()
{
return id;
}
/**
* The name of the schema as defined in the MCAP schema file, {@link Schema#name()}.
*
* @return the name of the schema.
*/
public String getName()
{
return name;
}
/**
* Whether this schema is an enum.
*
* @return {@code true} if this schema is an enum, {@code false} otherwise.
*/
public boolean isEnum()
{
return isEnum;
}
/**
* The enum constants of the schema as defined in the MCAP schema file.
*
* @return the enum constants of the schema.
*/
public String[] getEnumConstants()
{
return enumConstants;
}
/**
* The fields declared in the schema.
*
* The fields can be of primitive type, array, vector, or sub-schema.
*
*
* @return the fields of the schema.
*/
public List getFields()
{
return fields;
}
public Map getSubSchemaMap()
{
return subSchemaMap;
}
/**
* Indicates whether this schema is already flat.
*
* @return {@code true} if this schema is already flat, {@code false} otherwise.
*/
public boolean isSchemaFlat()
{
return subSchemaMap == null || subSchemaMap.isEmpty();
}
public List getStaticFields()
{
return staticFields;
}
/**
* Returns a schema equivalent to this one but with all the complex types flattened, i.e. all the fields that are arrays or sub-schemas are expanded into
* multiple fields.
*
* @return the flattened schema.
*/
public MCAPSchema flattenSchema()
{
List flattenedFields = new ArrayList<>();
for (MCAPSchemaField field : fields)
{
flattenedFields.addAll(flattenField(field));
}
MCAPSchema mcapSchema = new MCAPSchema(name, id, flattenedFields, null);
mcapSchema.getStaticFields().addAll(staticFields);
return mcapSchema;
}
List flattenField(MCAPSchemaField field)
{
MCAPSchemaField flatField = field.clone();
if (!field.isComplexType())
{
return Collections.singletonList(flatField);
}
List flatFields = new ArrayList<>();
flatFields.add(flatField);
if (flatField.isArray())
{
for (int i = 0; i < flatField.getMaxLength(); i++)
{
MCAPSchemaField subField = new MCAPSchemaField();
subField.setParent(flatField);
subField.setType(flatField.getType());
subField.setName(flatField.getName() + "[" + i + "]");
subField.setArray(false);
subField.setVector(false);
subField.setMaxLength(-1);
flatFields.add(subField);
}
}
else
{
MCAPSchema subSchema = subSchemaMap.get(flatField.getType());
if (subSchema != null)
{
for (MCAPSchemaField subField : subSchema.fields)
{
subField.setParent(flatField);
subField.setName(flatField.getName() + "." + subField.getName());
flatFields.add(subField);
}
}
}
return flatFields;
}
@Override
public String toString()
{
return toString(0);
}
public String toString(int indent)
{
String out = getClass().getSimpleName() + ":";
out += "\n\t-name=" + name;
if (fields != null)
out += "\n\t-fields=\n" + EuclidCoreIOTools.getCollectionString("\n", fields, f -> f.toString(indent + 2));
if (enumConstants != null)
out += "\n\t-enumConstants=" + Arrays.toString(enumConstants);
if (!staticFields.isEmpty())
out += "\n\t-staticFields=\n" + EuclidCoreIOTools.getCollectionString("\n", staticFields, f -> f.toString(indent + 2));
if (subSchemaMap != null)
out += "\n\t-subSchemaMap=\n" + indentString(indent + 2) + EuclidCoreIOTools.getCollectionString("\n" + indentString(indent + 2),
subSchemaMap.entrySet(),
e -> e.getKey() + "->\n" + e.getValue()
.toString(indent + 3)
.replace("^(\t*)", ""));
return indent(out, indent);
}
public static String mcapMCAPMessageToString(Message message, MCAPSchema schema)
{
CDRDeserializer cdr = new CDRDeserializer();
cdr.initialize(message.messageBuffer(), 0, message.dataLength());
String output = mcapMCAPMessageToString(cdr, schema, 0);
cdr.finalize(true);
return output;
}
private static String mcapMCAPMessageToString(CDRDeserializer cdr, MCAPSchema schema, int indent)
{
StringBuilder out = new StringBuilder(schema.getName() + ":");
for (MCAPSchemaField field : schema.getFields())
{
String fieldToString = mcapMCAPMessageFieldToString(cdr, field, schema, indent + 1);
if (fieldToString != null)
out.append(fieldToString);
}
return out.toString();
}
private static String mcapMCAPMessageFieldToString(CDRDeserializer cdr, MCAPSchemaField field, MCAPSchema schema, int indent)
{
if (schema == null && field.isComplexType())
{ // Dealing with a flat schema, skip this field.
return null;
}
StringBuilder out = new StringBuilder("\n" + indentString(indent) + field.getName() + ": ");
if (field.isArray())
{
out.append("[");
for (int i = 0; i < field.getMaxLength(); i++)
{
out.append("\n").append(indentString(indent + 1)).append(i).append(": ");
out.append(mcapMCAPMessageFieldToString(cdr, field, schema, indent + 2));
}
out.append("\n").append(indentString(indent)).append("]");
}
else
{
String fieldValue = null;
try
{
fieldValue = cdr.readTypeAsString(CDRDeserializer.Type.parseType(field.getType()), field.getMaxLength());
}
catch (IllegalArgumentException e)
{
// Ignore
}
if (fieldValue == null)
{
MCAPSchema subSchema = schema.getSubSchemaMap() == null ? null : schema.getSubSchemaMap().get(field.getType());
if (subSchema != null)
{
fieldValue = "\n" + indentString(indent + 1) + mcapMCAPMessageToString(cdr, subSchema, indent + 1);
}
else if (!schema.isSchemaFlat())
{
fieldValue = "Unknown type: " + field.getType();
}
}
out.append(fieldValue);
}
return out.toString();
}
/**
* Interface used to represent a field of a MCAP schema.
*
* A field can be a primitive type, an array, a vector, or a sub-schema.
*
*/
public static final class MCAPSchemaField
{
private MCAPSchemaField parent;
private String name;
private String type;
private boolean isArray;
private boolean isVector;
private int maxLength;
private boolean isComplexType;
// Used for static fields
private String defaultValue;
public MCAPSchemaField()
{
}
public MCAPSchemaField(String name, String type, boolean isArray, boolean isVector, int maxLength, boolean isComplexType)
{
this.name = name;
this.type = type;
this.isArray = isArray;
this.isVector = isVector;
this.maxLength = maxLength;
this.isComplexType = isComplexType;
}
public MCAPSchemaField(MCAPSchemaField other)
{
this.name = other.name;
this.type = other.type;
this.isArray = other.isArray;
this.isVector = other.isVector;
this.maxLength = other.maxLength;
this.isComplexType = other.isComplexType;
}
@Override
public MCAPSchemaField clone()
{
return new MCAPSchemaField(this);
}
public void setName(String name)
{
this.name = name;
}
public void setType(String type)
{
this.type = type;
}
public void setParent(MCAPSchemaField parent)
{
this.parent = parent;
}
public void setArray(boolean array)
{
isArray = array;
}
public void setVector(boolean vector)
{
isVector = vector;
}
public void setMaxLength(int maxLength)
{
this.maxLength = maxLength;
}
public void setComplexType(boolean complexType)
{
isComplexType = complexType;
}
/**
* The default value of the field as defined in the MCAP schema.
*
* This is only used for static fields.
*
*
* @param defaultValue the default value of the field.
*/
public void setDefaultValue(String defaultValue)
{
this.defaultValue = defaultValue;
}
/**
* The name of the field as defined in the MCAP schema.
*
* @return the name of the field.
*/
public String getName()
{
return name;
}
/**
* The type of the field as defined in the MCAP schema.
*
* @return the type of the field.
*/
public String getType()
{
return type;
}
/**
* The parent schema for this field.
*
* @return the parent schema of the field.
*/
public MCAPSchemaField getParent()
{
return parent;
}
/**
* Whether this field is an array.
*
* @return {@code true} if this field is an array, {@code false} otherwise.
*/
public boolean isArray()
{
return isArray;
}
public boolean isVector()
{
return isVector;
}
/**
* Whether this field is a vector.
*
* @return {@code true} if this field is a vector, {@code false} otherwise.
*/
public int getMaxLength()
{
return maxLength;
}
public String getDefaultValue()
{
return defaultValue;
}
/**
* Whether this field is a complex type such as an array, a vector, or a sub-schema.
*
* @return {@code true} if this field is complex, {@code false} otherwise.
*/
public boolean isComplexType()
{
return isComplexType;
}
@Override
public String toString()
{
return toString(0);
}
public String toString(int indent)
{
String out = getClass().getSimpleName() + ":";
out += "\n\t-type=" + type;
out += "\n\t-name=" + name;
out += "\n\t-isArray=" + isArray;
out += "\n\t-isVector=" + isVector;
out += "\n\t-isComplexType=" + isComplexType;
if (isArray || isVector)
out += "\n\t-maxLength=" + maxLength;
out += "\n\t-parent=" + (parent == null ? "null" : parent.name);
out += "\n";
return indent(out, indent);
}
}
public static String indent(String stringToIndent, int indent)
{
if (indent <= 0)
return stringToIndent;
String indentStr = indentString(indent);
return indentStr + stringToIndent.replace("\n", "\n" + indentStr);
}
public static String indentString(int indent)
{
return "\t".repeat(indent);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy