Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 124 additions & 85 deletions commons/src/main/java/com/powsybl/commons/binary/BinReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,24 @@
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.function.Supplier;

import static com.powsybl.commons.binary.BinUtil.END_NODE;
import static com.powsybl.commons.binary.BinUtil.NULL_ENUM;
import static com.powsybl.commons.binary.BinUtil.*;

/**
* @author Florian Dupuy {@literal <florian.dupuy at rte-france.com>}
*/
public class BinReader implements TreeDataReader {

private final DataInputStream dis;
private final Map<Integer, String> dictionary = new HashMap<>();
private final byte[] binaryMagicNumber;

private String[] nodeNames;

private String[] attrNames;
private byte[] attrTypes;

private int nextAttrIdx = END_ATTRS;

public BinReader(InputStream is, byte[] binaryMagicNumber) {
this.binaryMagicNumber = binaryMagicNumber;
this.dis = new DataInputStream(new BufferedInputStream(Objects.requireNonNull(is)));
Expand All @@ -38,7 +42,9 @@
try {
readMagicNumber();
TreeDataHeader header = new TreeDataHeader(readString(), readExtensionVersions());
readDictionary();
readNodeDictionary();
readAttrDictionary();
nextAttrIdx = dis.readUnsignedByte();
return header;
} catch (IOException e) {
throw new UncheckedIOException(e);
Expand All @@ -61,194 +67,226 @@
return versions;
}

private void readDictionary() throws IOException {
private void readNodeDictionary() throws IOException {
int nbEntries = dis.readShort();
nodeNames = new String[nbEntries + 1];
for (int i = 0; i < nbEntries; i++) {
dictionary.put(i + 1, readString());
}
}

private String readString() {
try {
int stringNbBytes = dis.readShort();
if (stringNbBytes == -1) {
return null;
}
byte[] stringBytes = dis.readNBytes(stringNbBytes);
if (stringBytes.length != stringNbBytes) {
// this may happen when the attribute wasn't written in the first place, causing string length to be an aberrant number
throw new PowsyblException("Cannot read the full string, bytes missing: " + (stringNbBytes - stringBytes.length));
}
return new String(stringBytes, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(e);
nodeNames[i + 1] = readString();
}
}

private double readDouble() {
try {
return dis.readDouble();
} catch (IOException e) {
throw new UncheckedIOException(e);
private void readAttrDictionary() throws IOException {
int nbEntries = dis.readShort();
attrNames = new String[nbEntries + 1];
attrTypes = new byte[nbEntries + 1];
for (int i = 0; i < nbEntries; i++) {
attrNames[i + 1] = readString();
attrTypes[i + 1] = dis.readByte();
}
}

private float readFloat() {
private Object readAttrValue(String name) {
try {
return dis.readFloat();
if (nextAttrIdx == END_ATTRS) {
return null;
}
String attrName = attrNames[nextAttrIdx];
if (attrName == null) {
throw new PowsyblException("Cannot read attribute: unknown attribute name index " + nextAttrIdx);
}
if (!name.equals(attrName)) {
return null;
}
byte typeTag = attrTypes[nextAttrIdx];
Object value = readTypedValue(typeTag);
nextAttrIdx = dis.readUnsignedByte();
return value;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

private int readInt() {
try {
return dis.readInt();
} catch (IOException e) {
throw new UncheckedIOException(e);
private void skipRemainingAttributes() throws IOException {
while (nextAttrIdx != END_ATTRS) {
readTypedValue(attrTypes[nextAttrIdx]);
nextAttrIdx = dis.readUnsignedByte();
}
}

private boolean readBoolean() {
try {
return dis.readBoolean();
} catch (IOException e) {
throw new UncheckedIOException(e);
private Object readTypedValue(byte typeTag) throws IOException {
return switch (typeTag) {
case TYPE_DOUBLE -> dis.readDouble();
case TYPE_FLOAT -> dis.readFloat();
case TYPE_INT -> dis.readInt();
case TYPE_BOOLEAN -> dis.readBoolean();
case TYPE_STRING -> readString();
case TYPE_ENUM -> (int) dis.readShort();
case TYPE_INT_ARRAY -> readIntArrayRaw();
case TYPE_STRING_ARRAY -> readStringArrayRaw();
default -> throw new PowsyblException("Binary format: unknown attribute type tag " + typeTag);
};
}

private List<Integer> readIntArrayRaw() throws IOException {
int count = dis.readShort();
List<Integer> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
list.add(dis.readInt());
}
return list;
}

private <T extends Enum<T>> T readEnum(Class<T> clazz) {
try {
short ordinal = dis.readShort();
return ordinal != NULL_ENUM ? clazz.getEnumConstants()[ordinal] : null;
} catch (IOException e) {
throw new UncheckedIOException(e);
private List<String> readStringArrayRaw() throws IOException {
int count = dis.readShort();
List<String> list = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
list.add(readString());
}
return list;
}

private <T> List<T> readArray(Supplier<T> valueReader) {
private String readString() {
try {
int nbValues = dis.readShort();
List<T> values = new ArrayList<>(nbValues);
for (int i = 0; i < nbValues; i++) {
values.add(valueReader.get());
int stringNbBytes = dis.readShort();
if (stringNbBytes == -1) {
return null;
}
return values;
byte[] stringBytes = dis.readNBytes(stringNbBytes);
if (stringBytes.length != stringNbBytes) {
throw new PowsyblException("Cannot read the full string, bytes missing: " + (stringNbBytes - stringBytes.length));
}
return new String(stringBytes, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}

@Override
public double readDoubleAttribute(String name) {
return readDouble();
Object val = readAttrValue(name);
return val instanceof Double d ? d : Double.NaN;
}

@Override
public double readDoubleAttribute(String name, double defaultValue) {
return readDouble();
Object val = readAttrValue(name);
return val instanceof Double d ? d : defaultValue;
}

@Override
public OptionalDouble readOptionalDoubleAttribute(String name) {
if (!readBoolean()) {
return OptionalDouble.empty();
}
return OptionalDouble.of(readDouble());
Object val = readAttrValue(name);
return val instanceof Double d ? OptionalDouble.of(d) : OptionalDouble.empty();
}

@Override
public float readFloatAttribute(String name) {
return readFloat();
Object val = readAttrValue(name);
return val instanceof Float f ? f : Float.NaN;
}

@Override
public float readFloatAttribute(String name, float defaultValue) {
return readFloat();
Object val = readAttrValue(name);
return val instanceof Float f ? f : defaultValue;
}

@Override
public String readStringAttribute(String name) {
return readString();
Object val = readAttrValue(name);
return val instanceof String s ? s : null;
}

@Override
public int readIntAttribute(String name) {
return readInt();
Object val = readAttrValue(name);
return val instanceof Integer i ? i : 0;
}

@Override
public OptionalInt readOptionalIntAttribute(String name) {
if (!readBoolean()) {
return OptionalInt.empty();
}
return OptionalInt.of(readInt());
public int readIntAttribute(String name, int defaultValue) {
Object val = readAttrValue(name);
return val instanceof Integer i ? i : defaultValue;
}

@Override
public int readIntAttribute(String name, int defaultValue) {
return readInt();
public OptionalInt readOptionalIntAttribute(String name) {
Object val = readAttrValue(name);
return val instanceof Integer i ? OptionalInt.of(i) : OptionalInt.empty();
}

@Override
public boolean readBooleanAttribute(String name) {
return readBoolean();
Object val = readAttrValue(name);
return val instanceof Boolean b ? b : false;

Check warning on line 221 in commons/src/main/java/com/powsybl/commons/binary/BinReader.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove the unnecessary boolean literal.

See more on https://sonarcloud.io/project/issues?id=com.powsybl%3Apowsybl-core&issues=AZyvDQ7R57f9iNlB4QRl&open=AZyvDQ7R57f9iNlB4QRl&pullRequest=3804
}

@Override
public boolean readBooleanAttribute(String name, boolean defaultValue) {
return readBoolean();
Object val = readAttrValue(name);
return val instanceof Boolean b ? b : defaultValue;
}

@Override
public Optional<Boolean> readOptionalBooleanAttribute(String name) {
if (!readBoolean()) {
return Optional.empty();
}
return Optional.of(readBoolean());
Object val = readAttrValue(name);
return val instanceof Boolean b ? Optional.of(b) : Optional.empty();
}

@Override
public <T extends Enum<T>> T readEnumAttribute(String name, Class<T> clazz) {
return readEnum(clazz);
return readEnumAttribute(name, clazz, null);
}

@Override
public <T extends Enum<T>> T readEnumAttribute(String name, Class<T> clazz, T defaultValue) {
return readEnum(clazz);
Object val = readAttrValue(name);
if (val instanceof Integer ordinal) {
T[] constants = clazz.getEnumConstants();
if (ordinal >= 0 && ordinal < constants.length) {
return constants[ordinal];
}
}
return defaultValue;
}

@Override
public String readContent() {
String content = readString();
Object val = readAttrValue(BinUtil.CONTENT_ATTR_NAME);
readEndNode();
return content;
return val instanceof String s ? s : null;
}

@Override
@SuppressWarnings("unchecked")
public List<Integer> readIntArrayAttribute(String name) {
return readArray(this::readInt);
Object val = readAttrValue(name);
return val instanceof List<?> list ? (List<Integer>) list : Collections.emptyList();
}

@Override
@SuppressWarnings("unchecked")
public List<String> readStringArrayAttribute(String name) {
return readArray(this::readString);
Object val = readAttrValue(name);
return val instanceof List<?> list ? (List<String>) list : Collections.emptyList();
}

@Override
public void skipNode() {
throw new PowsyblException("Binary format does not support skipping child nodes");
readChildNodes(nodeName -> skipNode());
}

@Override
public void readChildNodes(ChildNodeReader childNodeReader) {
try {
skipRemainingAttributes();
int nodeNameIndex;
while ((nodeNameIndex = dis.readShort()) != END_NODE) {
String nodeName = dictionary.get(nodeNameIndex);
String nodeName = nodeNames[nodeNameIndex];
if (nodeName == null) {
throw new PowsyblException("Cannot read child node: unknown element name index " + nodeNameIndex);
throw new PowsyblException("Cannot read child node: unknown node name index " + nodeNameIndex);
}
nextAttrIdx = dis.readUnsignedByte();
childNodeReader.onStartNode(nodeName);
}
} catch (IOException e) {
Expand All @@ -259,6 +297,7 @@
@Override
public void readEndNode() {
try {
skipRemainingAttributes();
int nextIndex = dis.readShort();
if (nextIndex != END_NODE) {
throw new PowsyblException("Binary parsing: expected end node but got " + nextIndex);
Expand Down
14 changes: 13 additions & 1 deletion commons/src/main/java/com/powsybl/commons/binary/BinUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ private BinUtil() {
}

static final int END_NODE = 0;
static final int NULL_ENUM = -1;
static final int END_ATTRS = 0;

static final byte TYPE_DOUBLE = 0;
static final byte TYPE_FLOAT = 1;
static final byte TYPE_INT = 2;
static final byte TYPE_BOOLEAN = 3;
static final byte TYPE_STRING = 4;
static final byte TYPE_ENUM = 5;
static final byte TYPE_INT_ARRAY = 6;
static final byte TYPE_STRING_ARRAY = 7;

// NUL prefix ensures this name cannot collide with any real XML attribute name
static final String CONTENT_ATTR_NAME = "\u0000content";

}
Loading