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