From a06a30a33507689464c736cb37c26445f232280e Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Tue, 24 May 2016 15:17:58 +0200 Subject: [PATCH] BUG-5280: optimize identifier serialization format Using ObjectOutput.writeObject() results in large overhead, resulting in transaction identifiers weighing in at 549 bytes when used in an object. Introduce WritableObject concept, which has a WriteTo(ObjectOutput) method and a static factory method readForm(ObjectInput). This effectively means we do not get Java Serialization headers. Also make FrontendType a final class, adopting the WritableObject serialization, which brings down the overhead to below 100 bytes. Change-Id: I20294d4fdf309f250d507dfc675d8405c1fcf505 Signed-off-by: Robert Varga --- .../access/concepts/ClientIdentifier.java | 50 +++++--- .../access/concepts/FrontendIdentifier.java | 51 +++++--- .../cluster/access/concepts/FrontendType.java | 121 +++++++++++++++++- .../concepts/LocalHistoryIdentifier.java | 44 ++++--- .../cluster/access/concepts/MemberName.java | 51 +++++--- .../concepts/TransactionIdentifier.java | 46 ++++--- .../access/concepts/WritableObject.java | 39 ++++++ .../access/concepts/WritableObjects.java | 103 +++++++++++++++ .../access/concepts/ClientIdentifierTest.java | 18 +-- .../concepts/FrontendIdentifierTest.java | 40 ++---- .../concepts/LocalHistoryIdentifierTest.java | 33 +++++ .../concepts/TransactionIdentifierTest.java | 34 +++++ .../access/concepts/WritableObjectsTest.java | 84 ++++++++++++ 13 files changed, 577 insertions(+), 137 deletions(-) create mode 100644 opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/WritableObject.java create mode 100644 opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/WritableObjects.java create mode 100644 opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/LocalHistoryIdentifierTest.java create mode 100644 opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/TransactionIdentifierTest.java create mode 100644 opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/WritableObjectsTest.java diff --git a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/ClientIdentifier.java b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/ClientIdentifier.java index b2016d806c..0a2ea5702c 100644 --- a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/ClientIdentifier.java +++ b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/ClientIdentifier.java @@ -10,6 +10,8 @@ package org.opendaylight.controller.cluster.access.concepts; import com.google.common.annotations.Beta; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; +import java.io.DataInput; +import java.io.DataOutput; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; @@ -23,54 +25,64 @@ import org.opendaylight.yangtools.concepts.Identifier; * @author Robert Varga */ @Beta -public final class ClientIdentifier implements Identifier { - private static final class Proxy implements Externalizable { +public final class ClientIdentifier implements Identifier, WritableObject { + private static final class Proxy implements Externalizable { private static final long serialVersionUID = 1L; - private FrontendIdentifier frontendId; + private FrontendIdentifier frontendId; private long generation; public Proxy() { // Needed for Externalizable } - Proxy(final FrontendIdentifier frontendId, final long generation) { + Proxy(final FrontendIdentifier frontendId, final long generation) { this.frontendId = Preconditions.checkNotNull(frontendId); this.generation = generation; } @Override - public void writeExternal(ObjectOutput out) throws IOException { - out.writeObject(frontendId); - out.writeLong(generation); + public void writeExternal(final ObjectOutput out) throws IOException { + frontendId.writeTo(out); + WritableObjects.writeLong(out, generation); } - @SuppressWarnings("unchecked") @Override - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - frontendId = (FrontendIdentifier) in.readObject(); - generation = in.readLong(); + public void readExternal(final ObjectInput in) throws IOException { + frontendId = FrontendIdentifier.readFrom(in); + generation = WritableObjects.readLong(in); } private Object readResolve() { - return new ClientIdentifier<>(frontendId, generation); + return new ClientIdentifier(frontendId, generation); } } private static final long serialVersionUID = 1L; - private final FrontendIdentifier frontendId; + private final FrontendIdentifier frontendId; private final long generation; - ClientIdentifier(final FrontendIdentifier frontendId, final long generation) { + ClientIdentifier(final FrontendIdentifier frontendId, final long generation) { this.frontendId = Preconditions.checkNotNull(frontendId); this.generation = generation; } - public static ClientIdentifier create(final FrontendIdentifier frontendId, + public static ClientIdentifier create(final FrontendIdentifier frontendId, final long generation) { - return new ClientIdentifier<>(frontendId, generation); + return new ClientIdentifier(frontendId, generation); } - public FrontendIdentifier getFrontendId() { + public static ClientIdentifier readFrom(final DataInput in) throws IOException { + final FrontendIdentifier frontendId = FrontendIdentifier.readFrom(in); + return new ClientIdentifier(frontendId, WritableObjects.readLong(in)); + } + + @Override + public void writeTo(final DataOutput out) throws IOException { + frontendId.writeTo(out); + WritableObjects.writeLong(out, generation); + } + + public FrontendIdentifier getFrontendId() { return frontendId; } @@ -92,7 +104,7 @@ public final class ClientIdentifier implements Identifie return false; } - final ClientIdentifier other = (ClientIdentifier) o; + final ClientIdentifier other = (ClientIdentifier) o; return generation == other.generation && frontendId.equals(other.frontendId); } @@ -103,6 +115,6 @@ public final class ClientIdentifier implements Identifie } private Object writeReplace() { - return new Proxy<>(frontendId, generation); + return new Proxy(frontendId, generation); } } diff --git a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/FrontendIdentifier.java b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/FrontendIdentifier.java index bd16e4b28e..54ed314b66 100644 --- a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/FrontendIdentifier.java +++ b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/FrontendIdentifier.java @@ -10,6 +10,8 @@ package org.opendaylight.controller.cluster.access.concepts; import com.google.common.annotations.Beta; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; +import java.io.DataInput; +import java.io.DataOutput; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; @@ -23,53 +25,64 @@ import org.opendaylight.yangtools.concepts.Identifier; * @author Robert Varga */ @Beta -public final class FrontendIdentifier implements Identifier { - private static final class Proxy implements Externalizable { +public final class FrontendIdentifier implements Identifier, WritableObject { + private static final class Proxy implements Externalizable { private static final long serialVersionUID = 1L; private MemberName memberName; - private T clientType; + private FrontendType clientType; public Proxy() { // Needed for Externalizable } - Proxy(final MemberName memberName, final T clientType) { + Proxy(final MemberName memberName, final FrontendType clientType) { this.memberName = Preconditions.checkNotNull(memberName); this.clientType = Preconditions.checkNotNull(clientType); } @Override - public void writeExternal(ObjectOutput out) throws IOException { - out.writeObject(memberName); - out.writeObject(clientType); + public void writeExternal(final ObjectOutput out) throws IOException { + memberName.writeTo(out); + clientType.writeTo(out); } - @SuppressWarnings("unchecked") @Override - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { - memberName = (MemberName) in.readObject(); - clientType = (T) in.readObject(); + public void readExternal(final ObjectInput in) throws IOException { + memberName = MemberName.readFrom(in); + clientType = FrontendType.readFrom(in); } private Object readResolve() { - return new FrontendIdentifier<>(memberName, clientType); + return new FrontendIdentifier(memberName, clientType); } } private static final long serialVersionUID = 1L; private final MemberName memberName; - private final T clientType; + private final FrontendType clientType; - FrontendIdentifier(final MemberName memberName, final T clientType) { + FrontendIdentifier(final MemberName memberName, final FrontendType clientType) { this.clientType = Preconditions.checkNotNull(clientType); this.memberName = Preconditions.checkNotNull(memberName); } - public static FrontendIdentifier create(MemberName memberName, final T clientType) { - return new FrontendIdentifier<>(memberName, clientType); + public static FrontendIdentifier create(final MemberName memberName, final FrontendType clientType) { + return new FrontendIdentifier(memberName, clientType); } - public T getClientType() { + public static FrontendIdentifier readFrom(final DataInput in) throws IOException { + final MemberName memberName = MemberName.readFrom(in); + final FrontendType clientType = FrontendType.readFrom(in); + return new FrontendIdentifier(memberName, clientType); + } + + @Override + public void writeTo(final DataOutput out) throws IOException { + memberName.writeTo(out); + clientType.writeTo(out); + } + + public FrontendType getClientType() { return clientType; } @@ -91,7 +104,7 @@ public final class FrontendIdentifier implements Identif return false; } - final FrontendIdentifier other = (FrontendIdentifier) o; + final FrontendIdentifier other = (FrontendIdentifier) o; return memberName.equals(other.memberName) && clientType.equals(other.clientType); } @@ -102,6 +115,6 @@ public final class FrontendIdentifier implements Identif } private Object writeReplace() { - return new Proxy<>(memberName, clientType); + return new Proxy(memberName, clientType); } } diff --git a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/FrontendType.java b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/FrontendType.java index 27db82068d..806a7ad104 100644 --- a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/FrontendType.java +++ b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/FrontendType.java @@ -8,8 +8,18 @@ package org.opendaylight.controller.cluster.access.concepts; import com.google.common.annotations.Beta; +import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.base.Verify; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.Externalizable; +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.nio.charset.StandardCharsets; import java.util.regex.Pattern; -import javax.annotation.Nonnull; import org.opendaylight.yangtools.concepts.Identifier; /** @@ -20,16 +30,113 @@ import org.opendaylight.yangtools.concepts.Identifier; * @author Robert Varga */ @Beta -public interface FrontendType extends Identifier { - Pattern SIMPLE_STRING_PATTERN = Pattern.compile("[a-zA-Z-_.*+:=,!~';]+"); +public final class FrontendType implements Comparable, Identifier, WritableObject { + private static final class Proxy implements Externalizable { + private static final long serialVersionUID = 1L; + private byte[] serialized; + + public Proxy() { + // For Externalizable + } + + Proxy(final byte[] serialized) { + this.serialized = Preconditions.checkNotNull(serialized); + } + + @Override + public void writeExternal(final ObjectOutput out) throws IOException { + out.writeInt(serialized.length); + out.write(serialized); + } + + @Override + public void readExternal(final ObjectInput in) throws IOException { + serialized = new byte[in.readInt()]; + in.readFully(serialized); + } + + private Object readResolve() { + // TODO: consider caching instances here + return new FrontendType(new String(serialized, StandardCharsets.UTF_8), serialized); + } + } + + private static final Pattern SIMPLE_STRING_PATTERN = Pattern.compile("^[a-zA-Z-_.*+:=,!~';]+$"); + private static final long serialVersionUID = 1L; + private final String name; + private volatile byte[] serialized; + + private FrontendType(final String name) { + this.name = Preconditions.checkNotNull(name); + } + + FrontendType(final String name, final byte[] serialized) { + this(name); + this.serialized = Verify.verifyNotNull(serialized); + } /** - * Return a string representation of this frontend type. Unlike {@link #toString()}, returned string has - * restricted rules on what it can contain: + * Return a {@link FrontendType} corresponding to a string representation. Input string has constraints + * on what characters it can contain. It may contain the following: * - US-ASCII letters * - special characters: -_.*+:=,!~'; * - * A validation pattern for this string is available as {@link #SIMPLE_STRING_PATTERN}. + * @return A {@link FrontendType} instance + * @throws IllegalArgumentException if the string is null, empty or contains invalid characters */ - @Nonnull String toSimpleString(); + public static FrontendType forName(final String name) { + Preconditions.checkArgument(!Strings.isNullOrEmpty(name)); + Preconditions.checkArgument(SIMPLE_STRING_PATTERN.matcher(name).matches()); + return new FrontendType(name); + } + + public static FrontendType readFrom(final DataInput in) throws IOException { + final byte[] serialized = new byte[in.readInt()]; + in.readFully(serialized); + return new FrontendType(new String(serialized, StandardCharsets.UTF_8)); + } + + @Override + public void writeTo(final DataOutput out) throws IOException { + final byte[] serialized = getSerialized(); + out.writeInt(serialized.length); + out.write(serialized); + } + + public String getName() { + return name; + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(final Object o) { + return this == o || (o instanceof FrontendType && name.equals(((FrontendType)o).name)); + } + + @Override + public int compareTo(final FrontendType o) { + return this == o ? 0 : name.compareTo(o.name); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(FrontendType.class).add("name", name).toString(); + } + + private byte[] getSerialized() { + byte[] local = serialized; + if (local == null) { + local = name.getBytes(StandardCharsets.UTF_8); + serialized = local; + } + return local; + } + + Object writeReplace() { + return new Proxy(getSerialized()); + } } diff --git a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/LocalHistoryIdentifier.java b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/LocalHistoryIdentifier.java index 330362e6ae..4eb26270c4 100644 --- a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/LocalHistoryIdentifier.java +++ b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/LocalHistoryIdentifier.java @@ -9,6 +9,8 @@ package org.opendaylight.controller.cluster.access.concepts; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; +import java.io.DataInput; +import java.io.DataOutput; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; @@ -18,53 +20,61 @@ import org.opendaylight.yangtools.concepts.Identifier; /** * Globally-unique identifier of a local history. * - * @param Frontend type - * * @author Robert Varga */ -public final class LocalHistoryIdentifier implements Identifier { - private static final class Proxy implements Externalizable { +public final class LocalHistoryIdentifier implements Identifier, WritableObject { + private static final class Proxy implements Externalizable { private static final long serialVersionUID = 1L; - private ClientIdentifier clientId; + private ClientIdentifier clientId; private long historyId; public Proxy() { // For Externalizable } - Proxy(final ClientIdentifier frontendId, final long historyId) { + Proxy(final ClientIdentifier frontendId, final long historyId) { this.clientId = Preconditions.checkNotNull(frontendId); this.historyId = historyId; } @Override public void writeExternal(final ObjectOutput out) throws IOException { - out.writeObject(clientId); - out.writeLong(historyId); + clientId.writeTo(out); + WritableObjects.writeLong(out, historyId); } - @SuppressWarnings("unchecked") @Override public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { - clientId = (ClientIdentifier) in.readObject(); - historyId = in.readLong(); + clientId = ClientIdentifier.readFrom(in); + historyId = WritableObjects.readLong(in); } private Object readResolve() { - return new LocalHistoryIdentifier<>(clientId, historyId); + return new LocalHistoryIdentifier(clientId, historyId); } } private static final long serialVersionUID = 1L; - private final ClientIdentifier clientId; + private final ClientIdentifier clientId; private final long historyId; - public LocalHistoryIdentifier(final ClientIdentifier frontendId, final long historyId) { + public LocalHistoryIdentifier(final ClientIdentifier frontendId, final long historyId) { this.clientId = Preconditions.checkNotNull(frontendId); this.historyId = historyId; } - public ClientIdentifier getClienId() { + public static LocalHistoryIdentifier readFrom(final DataInput in) throws IOException { + final ClientIdentifier clientId = ClientIdentifier.readFrom(in); + return new LocalHistoryIdentifier(clientId, WritableObjects.readLong(in)); + } + + @Override + public void writeTo(final DataOutput out) throws IOException { + clientId.writeTo(out); + WritableObjects.writeLong(out, historyId); + } + + public ClientIdentifier getClientId() { return clientId; } @@ -86,7 +96,7 @@ public final class LocalHistoryIdentifier implements Ide return false; } - final LocalHistoryIdentifier other = (LocalHistoryIdentifier) o; + final LocalHistoryIdentifier other = (LocalHistoryIdentifier) o; return historyId == other.historyId && clientId.equals(other.clientId); } @@ -97,6 +107,6 @@ public final class LocalHistoryIdentifier implements Ide } private Object writeReplace() { - return new Proxy<>(clientId, historyId); + return new Proxy(clientId, historyId); } } diff --git a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/MemberName.java b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/MemberName.java index ceec55555f..a6794d0032 100644 --- a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/MemberName.java +++ b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/MemberName.java @@ -12,6 +12,8 @@ import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; import com.google.common.base.Strings; import com.google.common.base.Verify; +import java.io.DataInput; +import java.io.DataOutput; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; @@ -25,7 +27,7 @@ import org.opendaylight.yangtools.concepts.Identifier; * @author Robert Varga */ @Beta -public final class MemberName implements Comparable, Identifier { +public final class MemberName implements Comparable, Identifier, WritableObject { private static final class Proxy implements Externalizable { private static final long serialVersionUID = 1L; private byte[] serialized; @@ -34,8 +36,8 @@ public final class MemberName implements Comparable, Identifier { // For Externalizable } - Proxy(final String name) { - serialized = name.getBytes(StandardCharsets.UTF_8); + Proxy(final byte[] serialized) { + this.serialized = Preconditions.checkNotNull(serialized); } @Override @@ -45,28 +47,28 @@ public final class MemberName implements Comparable, Identifier { } @Override - public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + public void readExternal(ObjectInput in) throws IOException { serialized = new byte[in.readInt()]; in.readFully(serialized); } private Object readResolve() { // TODO: consider caching instances here - return new MemberName(new String(serialized, StandardCharsets.UTF_8), this); + return new MemberName(new String(serialized, StandardCharsets.UTF_8), serialized); } } private static final long serialVersionUID = 1L; private final String name; - private volatile Proxy proxy; + private volatile byte[] serialized; - MemberName(final String name) { + private MemberName(final String name) { this.name = Preconditions.checkNotNull(name); } - MemberName(final String name, final Proxy proxy) { - this.name = Preconditions.checkNotNull(name); - this.proxy = Verify.verifyNotNull(proxy); + MemberName(final String name, final byte[] serialized) { + this(name); + this.serialized = Verify.verifyNotNull(serialized); } public static MemberName forName(final String name) { @@ -75,6 +77,19 @@ public final class MemberName implements Comparable, Identifier { return new MemberName(name); } + public static MemberName readFrom(final DataInput in) throws IOException { + final byte[] serialized = new byte[in.readInt()]; + in.readFully(serialized); + return new MemberName(new String(serialized, StandardCharsets.UTF_8)); + } + + @Override + public void writeTo(final DataOutput out) throws IOException { + final byte[] serialized = getSerialized(); + out.writeInt(serialized.length); + out.write(serialized); + } + public String getName() { return name; } @@ -99,14 +114,16 @@ public final class MemberName implements Comparable, Identifier { return MoreObjects.toStringHelper(MemberName.class).add("name", name).toString(); } - Object writeReplace() { - Proxy ret = proxy; - if (ret == null) { - // We do not really care if multiple threads race here - ret = new Proxy(name); - proxy = ret; + private byte[] getSerialized() { + byte[] local = serialized; + if (local == null) { + local = name.getBytes(StandardCharsets.UTF_8); + serialized = local; } + return local; + } - return ret; + Object writeReplace() { + return new Proxy(getSerialized()); } } diff --git a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/TransactionIdentifier.java b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/TransactionIdentifier.java index 2aaf8264aa..7d058ef9ff 100644 --- a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/TransactionIdentifier.java +++ b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/TransactionIdentifier.java @@ -10,6 +10,8 @@ package org.opendaylight.controller.cluster.access.concepts; import com.google.common.annotations.Beta; import com.google.common.base.MoreObjects; import com.google.common.base.Preconditions; +import java.io.DataInput; +import java.io.DataOutput; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; @@ -20,54 +22,62 @@ import org.opendaylight.yangtools.concepts.Identifier; /** * Globally-unique identifier of a transaction. * - * @param Frontend type - * * @author Robert Varga */ @Beta -public final class TransactionIdentifier implements Identifier { - private static final class Proxy implements Externalizable { +public final class TransactionIdentifier implements Identifier, WritableObject { + private static final class Proxy implements Externalizable { private static final long serialVersionUID = 1L; - private LocalHistoryIdentifier historyId; + private LocalHistoryIdentifier historyId; private long transactionId; public Proxy() { // For Externalizable } - Proxy(final LocalHistoryIdentifier historyId, final long transactionId) { + Proxy(final LocalHistoryIdentifier historyId, final long transactionId) { this.historyId = Preconditions.checkNotNull(historyId); this.transactionId = transactionId; } @Override public void writeExternal(final ObjectOutput out) throws IOException { - out.writeObject(historyId); - out.writeLong(transactionId); + historyId.writeTo(out); + WritableObjects.writeLong(out, transactionId); } - @SuppressWarnings("unchecked") @Override - public void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException { - historyId = (LocalHistoryIdentifier) in.readObject(); - transactionId = in.readLong(); + public void readExternal(final ObjectInput in) throws IOException { + historyId = LocalHistoryIdentifier.readFrom(in); + transactionId = WritableObjects.readLong(in); } private Object readResolve() { - return new TransactionIdentifier<>(historyId, transactionId); + return new TransactionIdentifier(historyId, transactionId); } } private static final long serialVersionUID = 1L; - private final LocalHistoryIdentifier historyId; + private final LocalHistoryIdentifier historyId; private final long transactionId; - public TransactionIdentifier(final @Nonnull LocalHistoryIdentifier historyId, final long transactionId) { + public TransactionIdentifier(final @Nonnull LocalHistoryIdentifier historyId, final long transactionId) { this.historyId = Preconditions.checkNotNull(historyId); this.transactionId = transactionId; } - public LocalHistoryIdentifier getHistoryId() { + public static TransactionIdentifier readFrom(final DataInput in) throws IOException { + final LocalHistoryIdentifier historyId = LocalHistoryIdentifier.readFrom(in); + return new TransactionIdentifier(historyId, WritableObjects.readLong(in)); + } + + @Override + public void writeTo(final DataOutput out) throws IOException { + historyId.writeTo(out); + WritableObjects.writeLong(out, transactionId); + } + + public LocalHistoryIdentifier getHistoryId() { return historyId; } @@ -89,7 +99,7 @@ public final class TransactionIdentifier implements Iden return false; } - final TransactionIdentifier other = (TransactionIdentifier) o; + final TransactionIdentifier other = (TransactionIdentifier) o; return transactionId == other.transactionId && historyId.equals(other.historyId); } @@ -100,6 +110,6 @@ public final class TransactionIdentifier implements Iden } private Object writeReplace() { - return new Proxy<>(historyId, transactionId); + return new Proxy(historyId, transactionId); } } diff --git a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/WritableObject.java b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/WritableObject.java new file mode 100644 index 0000000000..8869bf44ff --- /dev/null +++ b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/WritableObject.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.access.concepts; + +import com.google.common.annotations.Beta; +import java.io.DataOutput; +import java.io.IOException; +import javax.annotation.Nonnull; + +/** + * Marker interface for an object which can be written out to an {@link DataOutput}. Classes implementing this + * interface should declare a corresponding + * + *
+ *      public static CLASS readFrom(DataInput in) throws IOException;
+ * 
+ * + * The serialization format provided by this abstraction does not guarantee versioning. Callers are responsible for + * ensuring the source stream is correctly positioned. + * + * @author Robert Varga + */ +// FIXME: this really should go into yangtools/common/concepts. +@Beta +public interface WritableObject { + /** + * Serialize this object into a {@link DataOutput} as a fixed-format stream. + * + * @param out Output + * @throws IOException if the output fails + * @throws NullPointerException if out is null + */ + void writeTo(@Nonnull DataOutput out) throws IOException; +} diff --git a/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/WritableObjects.java b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/WritableObjects.java new file mode 100644 index 0000000000..463e5623fe --- /dev/null +++ b/opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/WritableObjects.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.access.concepts; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +//FIXME: this really should go into yangtools/common/concepts. +public final class WritableObjects { + private WritableObjects() { + throw new UnsupportedOperationException(); + } + + /** + * Write a long value into a {@link DataOutput}, compressing potential zero bytes. This method is useful for + * serializing counters and similar, which have a wide range, but typically do not use it. The value provided is + * treated as unsigned. + * + * This methods writes the number of trailing non-zero in the value. It then writes the minimum required bytes + * to reconstruct the value by left-padding zeroes. Inverse operation is performed by {@link #readLong(DataInput)}. + * + * @param out Output + * @param value long value to write + */ + public static void writeLong(final DataOutput out, final long value) throws IOException { + final int bytes = valueBytes(value); + out.writeByte(bytes); + writeValue(out, value, bytes); + } + + public static long readLong(final DataInput in) throws IOException { + return readValue(in, in.readByte()); + } + + private static long readValue(final DataInput in, final int flags) throws IOException { + int bytes = flags & 0xf; + if (bytes < 8) { + if (bytes > 0) { + long value = 0; + if (bytes >= 4) { + bytes -= 4; + value = (in.readInt() & 0xFFFFFFFFL) << (bytes * Byte.SIZE); + } + if (bytes >= 2) { + bytes -= 2; + value |= in.readUnsignedShort() << (bytes * Byte.SIZE); + } + if (bytes > 0) { + value |= in.readUnsignedByte(); + } + return value; + } else { + return 0; + } + } else { + return in.readLong(); + } + } + + private static void writeValue(final DataOutput out, final long value, final int bytes) throws IOException { + if (bytes < 8) { + int left = bytes; + if (left >= 4) { + left -= 4; + out.writeInt((int)(value >>> (left * Byte.SIZE))); + } + if (left >= 2) { + left -= 2; + out.writeShort((int)(value >>> (left * Byte.SIZE))); + } + if (left > 0) { + out.writeByte((int)(value & 0xFF)); + } + } else { + out.writeLong(value); + } + } + + private static int valueBytes(final long value) { + // This is a binary search for the first match. Note that we need to mask bits from the most significant one + if ((value & 0xFFFFFFFF00000000L) != 0) { + if ((value & 0xFFFF000000000000L) != 0) { + return (value & 0xFF00000000000000L) != 0 ? 8 : 7; + } else { + return (value & 0xFF0000000000L) != 0 ? 6 : 5; + } + } else if ((value & 0xFFFFFFFFL) != 0) { + if ((value & 0xFFFF0000L) != 0) { + return (value & 0xFF000000L) != 0 ? 4 : 3; + } else { + return (value & 0xFF00L) != 0 ? 2 : 1; + } + } else { + return 0; + } + } +} diff --git a/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/ClientIdentifierTest.java b/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/ClientIdentifierTest.java index 666c2b088a..4871def180 100644 --- a/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/ClientIdentifierTest.java +++ b/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/ClientIdentifierTest.java @@ -7,26 +7,26 @@ */ package org.opendaylight.controller.cluster.access.concepts; -public class ClientIdentifierTest extends AbstractIdentifierTest> { - private static final FrontendIdentifier FRONTEND = - new FrontendIdentifier<>(MemberName.forName("test"), FrontendIdentifierTest.ONE_FRONTEND_TYPE); +public class ClientIdentifierTest extends AbstractIdentifierTest { + private static final FrontendIdentifier FRONTEND = + new FrontendIdentifier(MemberName.forName("test"), FrontendIdentifierTest.ONE_FRONTEND_TYPE); - private static final ClientIdentifier OBJECT = new ClientIdentifier<>(FRONTEND, 0); - private static final ClientIdentifier DIFFERENT_OBJECT = new ClientIdentifier<>(FRONTEND, 1); - private static final ClientIdentifier EQUAL_OBJECT = new ClientIdentifier<>(FRONTEND, 0); + private static final ClientIdentifier OBJECT = new ClientIdentifier(FRONTEND, 0); + private static final ClientIdentifier DIFFERENT_OBJECT = new ClientIdentifier(FRONTEND, 1); + private static final ClientIdentifier EQUAL_OBJECT = new ClientIdentifier(FRONTEND, 0); @Override - ClientIdentifier object() { + ClientIdentifier object() { return OBJECT; } @Override - ClientIdentifier differentObject() { + ClientIdentifier differentObject() { return DIFFERENT_OBJECT; } @Override - ClientIdentifier equalObject() { + ClientIdentifier equalObject() { return EQUAL_OBJECT; } } diff --git a/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/FrontendIdentifierTest.java b/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/FrontendIdentifierTest.java index 5611bb5b16..cc56fc1101 100644 --- a/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/FrontendIdentifierTest.java +++ b/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/FrontendIdentifierTest.java @@ -7,49 +7,27 @@ */ package org.opendaylight.controller.cluster.access.concepts; -public class FrontendIdentifierTest extends AbstractIdentifierTest> { - static final FrontendType ONE_FRONTEND_TYPE = new FrontendType() { - private static final long serialVersionUID = 1L; - - @Override - public String toSimpleString() { - return "one"; - } - - private Object readResolve() { - return ONE_FRONTEND_TYPE; - } - }; - static final FrontendType OTHER_FRONTEND_TYPE = new FrontendType() { - private static final long serialVersionUID = 1L; - - @Override - public String toSimpleString() { - return "two"; - } - - private Object readResolve() { - return OTHER_FRONTEND_TYPE; - } - }; +public class FrontendIdentifierTest extends AbstractIdentifierTest { + static final FrontendType ONE_FRONTEND_TYPE = FrontendType.forName("one"); + static final FrontendType OTHER_FRONTEND_TYPE = FrontendType.forName("two"); private static final MemberName MEMBER = MemberName.forName("test"); - private static final FrontendIdentifier OBJECT = new FrontendIdentifier<>(MEMBER, ONE_FRONTEND_TYPE); - private static final FrontendIdentifier DIFFERENT_OBJECT = new FrontendIdentifier<>(MEMBER, OTHER_FRONTEND_TYPE); - private static final FrontendIdentifier EQUAL_OBJECT = new FrontendIdentifier<>(MEMBER, ONE_FRONTEND_TYPE); + private static final FrontendIdentifier OBJECT = new FrontendIdentifier(MEMBER, ONE_FRONTEND_TYPE); + private static final FrontendIdentifier DIFFERENT_OBJECT = new FrontendIdentifier(MEMBER, OTHER_FRONTEND_TYPE); + private static final FrontendIdentifier EQUAL_OBJECT = new FrontendIdentifier(MEMBER, ONE_FRONTEND_TYPE); @Override - FrontendIdentifier object() { + FrontendIdentifier object() { return OBJECT; } @Override - FrontendIdentifier differentObject() { + FrontendIdentifier differentObject() { return DIFFERENT_OBJECT; } @Override - FrontendIdentifier equalObject() { + FrontendIdentifier equalObject() { return EQUAL_OBJECT; } } diff --git a/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/LocalHistoryIdentifierTest.java b/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/LocalHistoryIdentifierTest.java new file mode 100644 index 0000000000..159896b9f4 --- /dev/null +++ b/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/LocalHistoryIdentifierTest.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.access.concepts; + +public class LocalHistoryIdentifierTest extends AbstractIdentifierTest { + private static final FrontendIdentifier FRONTEND = + new FrontendIdentifier(MemberName.forName("test"), FrontendIdentifierTest.ONE_FRONTEND_TYPE); + private static final ClientIdentifier CLIENT = new ClientIdentifier(FRONTEND, 0); + + private static final LocalHistoryIdentifier OBJECT = new LocalHistoryIdentifier(CLIENT, 0); + private static final LocalHistoryIdentifier DIFFERENT_OBJECT = new LocalHistoryIdentifier(CLIENT, 1); + private static final LocalHistoryIdentifier EQUAL_OBJECT = new LocalHistoryIdentifier(CLIENT, 0); + + @Override + LocalHistoryIdentifier object() { + return OBJECT; + } + + @Override + LocalHistoryIdentifier differentObject() { + return DIFFERENT_OBJECT; + } + + @Override + LocalHistoryIdentifier equalObject() { + return EQUAL_OBJECT; + } +} diff --git a/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/TransactionIdentifierTest.java b/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/TransactionIdentifierTest.java new file mode 100644 index 0000000000..e1c281ac2b --- /dev/null +++ b/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/TransactionIdentifierTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.access.concepts; + +public class TransactionIdentifierTest extends AbstractIdentifierTest { + private static final FrontendIdentifier FRONTEND = + new FrontendIdentifier(MemberName.forName("test"), FrontendIdentifierTest.ONE_FRONTEND_TYPE); + private static final ClientIdentifier CLIENT = new ClientIdentifier(FRONTEND, 0); + private static final LocalHistoryIdentifier HISTORY = new LocalHistoryIdentifier(CLIENT, 0); + + private static final TransactionIdentifier OBJECT = new TransactionIdentifier(HISTORY, 0); + private static final TransactionIdentifier DIFFERENT_OBJECT = new TransactionIdentifier(HISTORY, 1); + private static final TransactionIdentifier EQUAL_OBJECT = new TransactionIdentifier(HISTORY, 0); + + @Override + TransactionIdentifier object() { + return OBJECT; + } + + @Override + TransactionIdentifier differentObject() { + return DIFFERENT_OBJECT; + } + + @Override + TransactionIdentifier equalObject() { + return EQUAL_OBJECT; + } +} diff --git a/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/WritableObjectsTest.java b/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/WritableObjectsTest.java new file mode 100644 index 0000000000..bfccc3a25d --- /dev/null +++ b/opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/WritableObjectsTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2016 Cisco Systems, Inc. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.controller.cluster.access.concepts; + +import static org.junit.Assert.assertEquals; +import com.google.common.io.ByteArrayDataOutput; +import com.google.common.io.ByteStreams; +import java.io.IOException; +import org.junit.Test; + +public class WritableObjectsTest { + + private static void assertRecovery(final long expected) throws IOException { + final ByteArrayDataOutput out = ByteStreams.newDataOutput(); + WritableObjects.writeLong(out, expected); + final long actual = WritableObjects.readLong(ByteStreams.newDataInput(out.toByteArray())); + assertEquals(Long.toUnsignedString(expected, 16), Long.toUnsignedString(actual, 16)); + } + + @Test + public void testReadWriteLong() throws IOException { + assertRecovery(0L); + assertRecovery(1L); + assertRecovery(255L); + assertRecovery(256L); + + assertRecovery(Long.MAX_VALUE); + assertRecovery(Long.MIN_VALUE); + + assertRecovery(0xF000000000000000L); + assertRecovery(0x0F00000000000000L); + assertRecovery(0x00F0000000000000L); + assertRecovery(0x000F000000000000L); + assertRecovery(0x0000F00000000000L); + assertRecovery(0x00000F0000000000L); + assertRecovery(0x000000F000000000L); + assertRecovery(0x0000000F00000000L); + assertRecovery(0x00000000F0000000L); + assertRecovery(0x000000000F000000L); + assertRecovery(0x0000000000F00000L); + assertRecovery(0x00000000000F0000L); + assertRecovery(0x000000000000F000L); + assertRecovery(0x0000000000000F00L); + assertRecovery(0x00000000000000F0L); + + assertRecovery(0xF0F0F0F0F0F0F0F0L); + assertRecovery(0x0FF0F0F0F0F0F0F0L); + assertRecovery(0x00F0F0F0F0F0F0F0L); + assertRecovery(0x000FF0F0F0F0F0F0L); + assertRecovery(0x0000F0F0F0F0F0F0L); + assertRecovery(0x00000F00F0F0F0F0L); + assertRecovery(0x000000F0F0F0F0F0L); + assertRecovery(0x0000000FF0F0F0F0L); + assertRecovery(0x00000000F0F0F0F0L); + assertRecovery(0x000000000FF0F0F0L); + assertRecovery(0x0000000000F0F0F0L); + assertRecovery(0x00000000000FF0F0L); + assertRecovery(0x000000000000F0F0L); + assertRecovery(0x0000000000000FF0L); + assertRecovery(0x00000000000000F0L); + + assertRecovery(0x8000000000000000L); + assertRecovery(0x0800000000000000L); + assertRecovery(0x0080000000000000L); + assertRecovery(0x0008000000000000L); + assertRecovery(0x0000800000000000L); + assertRecovery(0x0000080000000000L); + assertRecovery(0x0000008000000000L); + assertRecovery(0x0000000800000000L); + assertRecovery(0x0000000080000000L); + assertRecovery(0x0000000008000000L); + assertRecovery(0x0000000000800000L); + assertRecovery(0x0000000000080000L); + assertRecovery(0x0000000000008000L); + assertRecovery(0x0000000000000800L); + assertRecovery(0x0000000000000080L); + assertRecovery(0x0000000000000008L); + } +} -- 2.36.6