BUG-5280: optimize identifier serialization format 88/39288/17
authorRobert Varga <rovarga@cisco.com>
Tue, 24 May 2016 13:17:58 +0000 (15:17 +0200)
committerTom Pantelis <tpanteli@brocade.com>
Wed, 1 Jun 2016 18:09:38 +0000 (18:09 +0000)
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 <rovarga@cisco.com>
13 files changed:
opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/ClientIdentifier.java
opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/FrontendIdentifier.java
opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/FrontendType.java
opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/LocalHistoryIdentifier.java
opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/MemberName.java
opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/TransactionIdentifier.java
opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/WritableObject.java [new file with mode: 0644]
opendaylight/md-sal/cds-access-api/src/main/java/org/opendaylight/controller/cluster/access/concepts/WritableObjects.java [new file with mode: 0644]
opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/ClientIdentifierTest.java
opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/FrontendIdentifierTest.java
opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/LocalHistoryIdentifierTest.java [new file with mode: 0644]
opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/TransactionIdentifierTest.java [new file with mode: 0644]
opendaylight/md-sal/cds-access-api/src/test/java/org/opendaylight/controller/cluster/access/concepts/WritableObjectsTest.java [new file with mode: 0644]

index b2016d8..0a2ea57 100644 (file)
@@ -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<T extends FrontendType> implements Identifier {
-    private static final class Proxy<T extends FrontendType> implements Externalizable {
+public final class ClientIdentifier implements Identifier, WritableObject {
+    private static final class Proxy implements Externalizable {
         private static final long serialVersionUID = 1L;
-        private FrontendIdentifier<T> frontendId;
+        private FrontendIdentifier frontendId;
         private long generation;
 
         public Proxy() {
             // Needed for Externalizable
         }
 
-        Proxy(final FrontendIdentifier<T> 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<T>) 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<T> frontendId;
+    private final FrontendIdentifier frontendId;
     private final long generation;
 
-    ClientIdentifier(final FrontendIdentifier<T> frontendId, final long generation) {
+    ClientIdentifier(final FrontendIdentifier frontendId, final long generation) {
         this.frontendId = Preconditions.checkNotNull(frontendId);
         this.generation = generation;
     }
 
-    public static <T extends FrontendType> ClientIdentifier<T> create(final FrontendIdentifier<T> frontendId,
+    public static ClientIdentifier create(final FrontendIdentifier frontendId,
             final long generation) {
-        return new ClientIdentifier<>(frontendId, generation);
+        return new ClientIdentifier(frontendId, generation);
     }
 
-    public FrontendIdentifier<T> 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<T extends FrontendType> 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<T extends FrontendType> implements Identifie
     }
 
     private Object writeReplace() {
-        return new Proxy<>(frontendId, generation);
+        return new Proxy(frontendId, generation);
     }
 }
index bd16e4b..54ed314 100644 (file)
@@ -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<T extends FrontendType> implements Identifier {
-    private static final class Proxy<T extends FrontendType> 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 <T extends FrontendType> FrontendIdentifier<T> 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<T extends FrontendType> 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<T extends FrontendType> implements Identif
     }
 
     private Object writeReplace() {
-        return new Proxy<>(memberName, clientType);
+        return new Proxy(memberName, clientType);
     }
 }
index 27db820..806a7ad 100644 (file)
@@ -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<FrontendType>, 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());
+    }
 }
index 330362e..4eb2627 100644 (file)
@@ -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 <T> Frontend type
- *
  * @author Robert Varga
  */
-public final class LocalHistoryIdentifier<T extends FrontendType> implements Identifier {
-    private static final class Proxy<T extends FrontendType> implements Externalizable {
+public final class LocalHistoryIdentifier implements Identifier, WritableObject {
+    private static final class Proxy implements Externalizable {
         private static final long serialVersionUID = 1L;
-        private ClientIdentifier<T> clientId;
+        private ClientIdentifier clientId;
         private long historyId;
 
         public Proxy() {
             // For Externalizable
         }
 
-        Proxy(final ClientIdentifier<T> 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<T>) 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<T> clientId;
+    private final ClientIdentifier clientId;
     private final long historyId;
 
-    public LocalHistoryIdentifier(final ClientIdentifier<T> frontendId, final long historyId) {
+    public LocalHistoryIdentifier(final ClientIdentifier frontendId, final long historyId) {
         this.clientId = Preconditions.checkNotNull(frontendId);
         this.historyId = historyId;
     }
 
-    public ClientIdentifier<T> 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<T extends FrontendType> 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<T extends FrontendType> implements Ide
     }
 
     private Object writeReplace() {
-        return new Proxy<>(clientId, historyId);
+        return new Proxy(clientId, historyId);
     }
 }
index ceec555..a6794d0 100644 (file)
@@ -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<MemberName>, Identifier {
+public final class MemberName implements Comparable<MemberName>, 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<MemberName>, 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<MemberName>, 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<MemberName>, 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<MemberName>, 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());
     }
 }
index 2aaf826..7d058ef 100644 (file)
@@ -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 <T> Frontend type
- *
  * @author Robert Varga
  */
 @Beta
-public final class TransactionIdentifier<T extends FrontendType> implements Identifier {
-    private static final class Proxy<T extends FrontendType> implements Externalizable {
+public final class TransactionIdentifier implements Identifier, WritableObject {
+    private static final class Proxy implements Externalizable {
         private static final long serialVersionUID = 1L;
-        private LocalHistoryIdentifier<T> historyId;
+        private LocalHistoryIdentifier historyId;
         private long transactionId;
 
         public Proxy() {
             // For Externalizable
         }
 
-        Proxy(final LocalHistoryIdentifier<T> 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<T>) 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<T> historyId;
+    private final LocalHistoryIdentifier historyId;
     private final long transactionId;
 
-    public TransactionIdentifier(final @Nonnull LocalHistoryIdentifier<T> historyId, final long transactionId) {
+    public TransactionIdentifier(final @Nonnull LocalHistoryIdentifier historyId, final long transactionId) {
         this.historyId = Preconditions.checkNotNull(historyId);
         this.transactionId = transactionId;
     }
 
-    public LocalHistoryIdentifier<T> 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<T extends FrontendType> 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<T extends FrontendType> 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 (file)
index 0000000..8869bf4
--- /dev/null
@@ -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
+ *
+ * <pre>
+ *      public static CLASS readFrom(DataInput in) throws IOException;
+ * </pre>
+ *
+ * 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 (file)
index 0000000..463e562
--- /dev/null
@@ -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;
+        }
+    }
+}
index 666c2b0..4871def 100644 (file)
@@ -7,26 +7,26 @@
  */
 package org.opendaylight.controller.cluster.access.concepts;
 
-public class ClientIdentifierTest extends AbstractIdentifierTest<ClientIdentifier<?>> {
-    private static final FrontendIdentifier<?> FRONTEND =
-            new FrontendIdentifier<>(MemberName.forName("test"), FrontendIdentifierTest.ONE_FRONTEND_TYPE);
+public class ClientIdentifierTest extends AbstractIdentifierTest<ClientIdentifier> {
+    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;
     }
 }
index 5611bb5..cc56fc1 100644 (file)
@@ -7,49 +7,27 @@
  */
 package org.opendaylight.controller.cluster.access.concepts;
 
-public class FrontendIdentifierTest extends AbstractIdentifierTest<FrontendIdentifier<?>> {
-    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<FrontendIdentifier> {
+    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 (file)
index 0000000..159896b
--- /dev/null
@@ -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<LocalHistoryIdentifier> {
+    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 (file)
index 0000000..e1c281a
--- /dev/null
@@ -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<TransactionIdentifier> {
+    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 (file)
index 0000000..bfccc3a
--- /dev/null
@@ -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);
+    }
+}

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.