57a3714257860bcacf8e6c12753a9689c14f828c
[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      * @throws NullPointerException if output is null
35      */
36     public static void writeLong(final DataOutput out, final long value) throws IOException {
37         writeLong(out, value, 0);
38     }
39
40     /**
41      * Write a long value into a {@link DataOutput}, compressing potential zero bytes. This method is useful for
42      * serializing counters and similar, which have a wide range, but typically do not use it. The value provided is
43      * treated as unsigned.
44      *
45      * <p>This methods writes the number of trailing non-zero in the value. It then writes the minimum required bytes
46      * to reconstruct the value by left-padding zeroes. Inverse operation is performed by {@link #readLong(DataInput)}
47      * or a combination of {@link #readLongHeader(DataInput)} and {@link #readLongBody(DataInput, byte)}.
48      *
49      * <p>Additionally the caller can use the top four bits (i.e. 0xF0) for caller-specific flags. These will be
50      * ignored by {@link #readLong(DataInput)}, but can be extracted via {@link #readLongHeader(DataInput)}.
51      *
52      * @param out Data output
53      * @param value long value to write
54      * @param flags flags to store
55      * @throws IOException if an I/O error occurs
56      * @throws NullPointerException if output is null
57      */
58     public static void writeLong(final DataOutput out, final long value, final int flags) throws IOException {
59         Preconditions.checkArgument((flags & 0xFFFFFF0F) == 0, "Invalid flags {}", flags);
60         final int bytes = valueBytes(value);
61         out.writeByte(bytes | flags);
62         writeValue(out, value, bytes);
63     }
64
65     /**
66      * Read a long value from a {@link DataInput} which was previously written via {@link #writeLong(DataOutput, long)}.
67      *
68      * @param in Data input
69      * @return long value extracted from the data input
70      * @throws IOException if an I/O error occurs
71      * @throws NullPointerException if input is null
72      */
73     public static long readLong(final @Nonnull DataInput in) throws IOException {
74         return readLongBody(in, readLongHeader(in));
75     }
76
77     /**
78      * Read the header of a compressed long value. The header may contain user-defined flags, which can be extracted
79      * via {@link #longHeaderFlags(byte)}.
80      *
81      * @param in Data input
82      * @return Header of next value
83      * @throws IOException if an I/O error occurs
84      * @throws NullPointerException if input is null
85      */
86     public static byte readLongHeader(final @Nonnull DataInput in) throws IOException {
87         return in.readByte();
88     }
89
90     /**
91      * Extract user-defined flags from a compressed long header. This will return 0 if the long value originates from
92      * {@link #writeLong(DataOutput, long)}.
93      *
94      * @param header Value header, as returned by {@link #readLongHeader(DataInput)}
95      * @return User-defined flags
96      */
97     public static int longHeaderFlags(final byte header) {
98         return header & 0xF0;
99     }
100
101     /**
102      * Read a long value from a {@link DataInput} as hinted by the result of {@link #readLongHeader(DataInput)}.
103      *
104      * @param in Data input
105      * @param header Value header, as returned by {@link #readLongHeader(DataInput)}
106      * @return long value
107      * @throws IOException if an I/O error occurs
108      * @throws NullPointerException if input is null
109      */
110     public static long readLongBody(final @Nonnull DataInput in, final byte header) throws IOException {
111         int bytes = header & 0xF;
112         if (bytes < 8) {
113             if (bytes > 0) {
114                 long value = 0;
115                 if (bytes >= 4) {
116                     bytes -= 4;
117                     value = (in.readInt() & 0xFFFFFFFFL) << (bytes * Byte.SIZE);
118                 }
119                 if (bytes >= 2) {
120                     bytes -= 2;
121                     value |= in.readUnsignedShort() << (bytes * Byte.SIZE);
122                 }
123                 if (bytes > 0) {
124                     value |= in.readUnsignedByte();
125                 }
126                 return value;
127             } else {
128                 return 0;
129             }
130         } else {
131             return in.readLong();
132         }
133     }
134
135     /**
136      * Write two consecutive long values. These values can be read back using {@link #readLongHeader(DataInput)},
137      * {@link #readFirstLong(DataInput, byte)} and {@link #readSecondLong(DataInput, byte)}.
138      *
139      * <p>This is a more efficient way of serializing two longs than {@link #writeLong(DataOutput, long)}. This is
140      * achieved by using the flags field to hold the length of the second long -- hence saving one byte.
141      *
142      * @param out Data output
143      * @param value0 first long value to write
144      * @param value1 second long value to write
145      * @throws IOException if an I/O error occurs
146      * @throws NullPointerException if output is null
147      */
148     public static void writeLongs(final @Nonnull DataOutput out, final long value0, final long value1)
149             throws IOException {
150         final int clen = WritableObjects.valueBytes(value1);
151         writeLong(out, value0, clen << 4);
152         WritableObjects.writeValue(out, value1, clen);
153     }
154
155     /**
156      * Read first long value from an input.
157      *
158      * @param in Data input
159      * @param header Value header, as returned by {@link #readLongHeader(DataInput)}
160      * @return First long specified in {@link #writeLongs(DataOutput, long, long)}
161      * @throws IOException if an I/O error occurs
162      * @throws NullPointerException if input is null
163      */
164     public static long readFirstLong(final @Nonnull DataInput in, final byte header) throws IOException {
165         return WritableObjects.readLongBody(in, header);
166     }
167
168     /**
169      * Read second long value from an input.
170      *
171      * @param in Data input
172      * @param header Value header, as returned by {@link #readLongHeader(DataInput)}
173      * @return Second long specified in {@link #writeLongs(DataOutput, long, long)}
174      * @throws IOException if an I/O error occurs
175      * @throws NullPointerException if input is null
176      */
177     public static long readSecondLong(final @Nonnull DataInput in, final byte header) throws IOException {
178         return WritableObjects.readLongBody(in, (byte)(header >>> 4));
179     }
180
181     private static void writeValue(final DataOutput out, final long value, final int bytes) throws IOException {
182         if (bytes < 8) {
183             int left = bytes;
184             if (left >= 4) {
185                 left -= 4;
186                 out.writeInt((int)(value >>> (left * Byte.SIZE)));
187             }
188             if (left >= 2) {
189                 left -= 2;
190                 out.writeShort((int)(value >>> (left * Byte.SIZE)));
191             }
192             if (left > 0) {
193                 out.writeByte((int)(value & 0xFF));
194             }
195         } else {
196             out.writeLong(value);
197         }
198     }
199
200     private static int valueBytes(final long value) {
201         // This is a binary search for the first match. Note that we need to mask bits from the most significant one.
202         // It completes completes in three to four mask-and-compare operations.
203         if ((value & 0xFFFFFFFF00000000L) != 0) {
204             if ((value & 0xFFFF000000000000L) != 0) {
205                 return (value & 0xFF00000000000000L) != 0 ? 8 : 7;
206             } else {
207                 return (value & 0x0000FF0000000000L) != 0 ? 6 : 5;
208             }
209         } else if ((value & 0x00000000FFFFFFFFL) != 0) {
210             if ((value & 0x00000000FFFF0000L) != 0) {
211                 return (value & 0x00000000FF000000L) != 0 ? 4 : 3;
212             } else {
213                 return (value & 0x000000000000FF00L) != 0 ? 2 : 1;
214             }
215         } else {
216             return 0;
217         }
218     }
219 }