import static java.util.Objects.requireNonNull;
-import java.io.DataInput;
-import java.io.DataOutput;
-import java.io.IOException;
import java.nio.file.attribute.BasicFileAttributes;
import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.yangtools.concepts.WritableObject;
-import org.opendaylight.yangtools.concepts.WritableObjects;
/**
* Hash of a single file state. {@link #size()} corresponds to {@link BasicFileAttributes#size()}.
*/
-record FileState(@NonNull String path, long size, int crc32) implements WritableObject {
+record FileState(@NonNull String path, long size, int crc32) {
FileState {
requireNonNull(path);
}
-
- public static FileState read(final DataInput in) throws IOException {
- return new FileState(in.readUTF(), WritableObjects.readLong(in), in.readInt());
- }
-
- @Override
- public void writeTo(final DataOutput out) throws IOException {
- out.writeUTF(path);
- WritableObjects.writeLong(out, size);
- out.writeInt(crc32);
- }
}
import static java.util.Objects.requireNonNull;
+import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
+import java.util.Set;
import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
import org.opendaylight.yangtools.concepts.WritableObject;
+import org.opendaylight.yangtools.concepts.WritableObjects;
/**
* A collection of {@link FileState} objects indexed by their {@link FileState#path()}.
*/
record FileStateSet(@NonNull ImmutableMap<String, FileState> fileStates) implements WritableObject {
+ private static final @NonNull FileStateSet EMPTY = new FileStateSet(ImmutableMap.of());
+
FileStateSet {
requireNonNull(fileStates);
}
+ static @NonNull FileStateSet empty() {
+ return EMPTY;
+ }
+
+ static @NonNull FileStateSet ofNullable(final @Nullable ImmutableMap<String, FileState> fileStates) {
+ return fileStates == null || fileStates.isEmpty() ? EMPTY : new FileStateSet(fileStates);
+ }
+
static @NonNull FileStateSet readFrom(final DataInput in) throws IOException {
final int size = in.readInt();
+ if (size == 0) {
+ return EMPTY;
+ }
+
+ final var prefix = in.readUTF();
final var fileStates = ImmutableMap.<String, FileState>builderWithExpectedSize(size);
for (int i = 0; i < size; ++i) {
- final var fileState = FileState.read(in);
- fileStates.put(fileState.path(), fileState);
+ final var path = prefix + in.readUTF();
+ fileStates.put(path, new FileState(path, WritableObjects.readLong(in), in.readInt()));
}
return new FileStateSet(fileStates.build());
}
@Override
public void writeTo(final DataOutput out) throws IOException {
out.writeInt(fileStates.size());
+ if (fileStates.isEmpty()) {
+ // Nothing else here
+ return;
+ }
+
+ final var prefix = findPathPrefix(fileStates.keySet());
+ out.writeUTF(prefix);
+
+ final int cutIndex = prefix.length();
for (var fileState : fileStates.values()) {
- // TODO: discover common prefix and serialize it just once -- but that will complicate things a log, as
- // we really maintain a hierarchy, which means we want the Map sorted in a certain way.
- fileState.writeTo(out);
+ final var path = fileState.path();
+ out.writeUTF(cutIndex == 0 ? path : path.substring(cutIndex));
+ WritableObjects.writeLong(out, fileState.size());
+ out.writeInt(fileState.crc32());
+ }
+ }
+
+ private static String findPathPrefix(final Set<String> filePaths) {
+ if (filePaths.size() == 1) {
+ // Single item: do not use a prefix
+ return "";
}
+
+ final var it = filePaths.iterator();
+ var prefix = it.next();
+
+ do {
+ prefix = Strings.commonPrefix(prefix, it.next());
+ } while (!prefix.isEmpty() && it.hasNext());
+
+ return prefix;
}
}