Introduce WritableObject/WritableIdentifier
[yangtools.git] / common / concepts / src / main / java / org / opendaylight / yangtools / concepts / WritableObjects.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.yangtools.concepts;
9
10 import com.google.common.annotations.Beta;
11 import com.google.common.base.Preconditions;
12 import java.io.DataInput;
13 import java.io.DataOutput;
14 import java.io.IOException;
15 import javax.annotation.Nonnull;
16
17 /**
18  * Utility methods for working with {@link WritableObject}s.
19  *
20  * @author Robert Varga
21  */
22 @Beta
23 public final class WritableObjects {
24     private WritableObjects() {
25         throw new UnsupportedOperationException();
26     }
27
28     /**
29      * Shorthand for {@link #writeLong(DataOutput, long, int)} with zero flags.
30      *
31      * @param out Data output
32      * @param value long value to write
33      * @throws IOException if an I/O error occurs
34      */
35     public static void writeLong(final DataOutput out, final long value) throws IOException {
36         writeLong(out, value, 0);
37     }
38
39     /**
40      * Write a long value into a {@link DataOutput}, compressing potential zero bytes. This method is useful for
41      * serializing counters and similar, which have a wide range, but typically do not use it. The value provided is
42      * treated as unsigned.
43      *
44      * This methods writes the number of trailing non-zero in the value. It then writes the minimum required bytes
45      * to reconstruct the value by left-padding zeroes. Inverse operation is performed by {@link #readLong(DataInput)}
46      * or a combination of {@link #readLongHeader(DataInput)} and {@link #readLongBody(DataInput, byte)}.
47      *
48      * Additionally the caller can use the top four bits (i.e. 0xF0) for caller-specific flags. These will be ignored
49      * by {@link #readLong(DataInput)}, but can be extracted via {@link #readLongHeader(DataInput)}.
50      *
51      * @param out Data output
52      * @param value long value to write
53      * @param flags flags to store
54      * @throws IOException if an I/O error occurs
55      */
56     public static void writeLong(final DataOutput out, final long value, final int flags) throws IOException {
57         Preconditions.checkArgument((flags & 0xFFFFFF0F) == 0, "Invalid flags {}", flags);
58         final int bytes = valueBytes(value);
59         out.writeByte(bytes | flags);
60         writeValue(out, value, bytes);
61     }
62
63     /**
64      * Read a long value from a {@link DataInput} which was previously written via {@link #writeLong(DataOutput, long)}.
65      *
66      * @param in Data input
67      * @return long value extracted from the data input
68      * @throws IOException if an I/O error occurs
69      */
70     public static long readLong(final @Nonnull DataInput in) throws IOException {
71         return readLongBody(in, readLongHeader(in));
72     }
73
74     /**
75      * Read the header of a compressed long value. The header may contain user-defined flags, which can be extracted
76      * via {@link #longHeaderFlags(byte)}.
77      *
78      * @param in Data input
79      * @return Header of next value
80      * @throws IOException if an I/O error occurs
81      */
82     public static byte readLongHeader(final @Nonnull DataInput in) throws IOException {
83         return in.readByte();
84     }
85
86     /**
87      * Extract user-defined flags from a compressed long header. This will return 0 if the long value originates from
88      * {@link #writeLong(DataOutput, long)}.
89      *
90      * @param header Value header, as returned by {@link #readLongHeader(DataInput)}
91      * @return User-defined flags
92      */
93     public static int longHeaderFlags(final byte header) {
94         return header & 0xF0;
95     }
96
97     /**
98      * Read a long value from a {@link DataInput} as hinted by the result of {@link #readLongHeader(DataInput)}.
99      *
100      * @param in Data input
101      * @param header Value header, as returned by {@link #readLongHeader(DataInput)}
102      * @throws IOException if an I/O error occurs
103      */
104     public static long readLongBody(final @Nonnull DataInput in, final byte header) throws IOException {
105         int bytes = header & 0xF;
106         if (bytes < 8) {
107             if (bytes > 0) {
108                 long value = 0;
109                 if (bytes >= 4) {
110                     bytes -= 4;
111                     value = (in.readInt() & 0xFFFFFFFFL) << (bytes * Byte.SIZE);
112                 }
113                 if (bytes >= 2) {
114                     bytes -= 2;
115                     value |= in.readUnsignedShort() << (bytes * Byte.SIZE);
116                 }
117                 if (bytes > 0) {
118                     value |= in.readUnsignedByte();
119                 }
120                 return value;
121             } else {
122                 return 0;
123             }
124         } else {
125             return in.readLong();
126         }
127     }
128
129     private static void writeValue(final DataOutput out, final long value, final int bytes) throws IOException {
130         if (bytes < 8) {
131             int left = bytes;
132             if (left >= 4) {
133                 left -= 4;
134                 out.writeInt((int)(value >>> (left * Byte.SIZE)));
135             }
136             if (left >= 2) {
137                 left -= 2;
138                 out.writeShort((int)(value >>> (left * Byte.SIZE)));
139             }
140             if (left > 0) {
141                 out.writeByte((int)(value & 0xFF));
142             }
143         } else {
144             out.writeLong(value);
145         }
146     }
147
148     private static int valueBytes(final long value) {
149         // This is a binary search for the first match. Note that we need to mask bits from the most significant one
150         if ((value & 0xFFFFFFFF00000000L) != 0) {
151             if ((value & 0xFFFF000000000000L) != 0) {
152                 return (value & 0xFF00000000000000L) != 0 ? 8 : 7;
153             } else {
154                 return (value & 0x0000FF0000000000L) != 0 ? 6 : 5;
155             }
156         } else if ((value & 0x00000000FFFFFFFFL) != 0) {
157             if ((value & 0x00000000FFFF0000L) != 0) {
158                 return (value & 0x00000000FF000000L) != 0 ? 4 : 3;
159             } else {
160                 return (value & 0x000000000000FF00L) != 0 ? 2 : 1;
161             }
162         } else {
163             return 0;
164         }
165     }
166 }