View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.types;
19  
20  import static org.junit.Assert.assertArrayEquals;
21  import static org.junit.Assert.assertEquals;
22  
23  import java.lang.reflect.Constructor;
24  import java.util.Arrays;
25  import java.util.Collection;
26  import java.util.Comparator;
27  
28  import org.apache.hadoop.hbase.testclassification.SmallTests;
29  import org.apache.hadoop.hbase.util.Bytes;
30  import org.apache.hadoop.hbase.util.Order;
31  import org.apache.hadoop.hbase.util.PositionedByteRange;
32  import org.apache.hadoop.hbase.util.SimplePositionedMutableByteRange;
33  import org.junit.Test;
34  import org.junit.experimental.categories.Category;
35  import org.junit.runner.RunWith;
36  import org.junit.runners.Parameterized;
37  import org.junit.runners.Parameterized.Parameters;
38  
39  /**
40   * This class both tests and demonstrates how to construct compound rowkeys
41   * from a POJO. The code under test is {@link Struct}.
42   * {@link SpecializedPojo1Type1} demonstrates how one might create their own
43   * custom data type extension for an application POJO.
44   */
45  @RunWith(Parameterized.class)
46  @Category(SmallTests.class)
47  public class TestStruct {
48    private Struct generic;
49    @SuppressWarnings("rawtypes")
50    private DataType specialized;
51    private Object[][] constructorArgs;
52  
53    public TestStruct(Struct generic, @SuppressWarnings("rawtypes") DataType specialized,
54        Object[][] constructorArgs) {
55      this.generic = generic;
56      this.specialized = specialized;
57      this.constructorArgs = constructorArgs;
58    }
59  
60    @Parameters
61    public static Collection<Object[]> params() {
62      Object[][] pojo1Args = {
63        new Object[] { "foo", 5,   10.001 },
64        new Object[] { "foo", 100, 7.0    },
65        new Object[] { "foo", 100, 10.001 },
66        new Object[] { "bar", 5,   10.001 },
67        new Object[] { "bar", 100, 10.001 },
68        new Object[] { "baz", 5,   10.001 },
69      };
70  
71      Object[][] pojo2Args = {
72        new Object[] { new byte[0], Bytes.toBytes("it"), "was", Bytes.toBytes("the") },
73        new Object[] { Bytes.toBytes("best"), new byte[0], "of", Bytes.toBytes("times,") },
74        new Object[] { Bytes.toBytes("it"), Bytes.toBytes("was"), "", Bytes.toBytes("the") },
75        new Object[] { Bytes.toBytes("worst"), Bytes.toBytes("of"), "times,", new byte[0] },
76        new Object[] { new byte[0], new byte[0], "", new byte[0] },
77      };
78  
79      Object[][] params = new Object[][] {
80          { SpecializedPojo1Type1.getGeneric(), new SpecializedPojo1Type1(), pojo1Args },
81          { SpecializedPojo2Type1.getGeneric(), new SpecializedPojo2Type1(), pojo2Args },
82      };
83      return Arrays.asList(params);
84    }
85  
86    static final Comparator<byte[]> NULL_SAFE_BYTES_COMPARATOR = new Comparator<byte[]>() {
87      @Override
88      public int compare(byte[] o1, byte[] o2) {
89        if (o1 == o2) {
90          return 0;
91        }
92        if (null == o1) {
93          return -1;
94        }
95        if (null == o2) {
96          return 1;
97        }
98        return Bytes.compareTo(o1, o2);
99      }
100   };
101 
102   /**
103    * A simple object to serialize.
104    */
105   private static class Pojo1 implements Comparable<Pojo1> {
106     final String stringFieldAsc;
107     final int intFieldAsc;
108     final double doubleFieldAsc;
109     final transient String str;
110 
111     public Pojo1(Object... argv) {
112       stringFieldAsc = (String) argv[0];
113       intFieldAsc = (Integer) argv[1];
114       doubleFieldAsc = (Double) argv[2];
115       str = new StringBuilder()
116             .append("{ ")
117             .append(null == stringFieldAsc ? "" : "\"")
118             .append(stringFieldAsc)
119             .append(null == stringFieldAsc ? "" : "\"").append(", ")
120             .append(intFieldAsc).append(", ")
121             .append(doubleFieldAsc)
122             .append(" }")
123             .toString();
124     }
125 
126     @Override
127     public String toString() {
128       return str;
129     }
130 
131     @Override
132     public int compareTo(Pojo1 o) {
133       int cmp = stringFieldAsc.compareTo(o.stringFieldAsc);
134       if (cmp != 0) {
135         return cmp;
136       }
137       cmp = Integer.valueOf(intFieldAsc).compareTo(Integer.valueOf(o.intFieldAsc));
138       if (cmp != 0) {
139         return cmp;
140       }
141       return Double.compare(doubleFieldAsc, o.doubleFieldAsc);
142     }
143 
144     @Override
145     public int hashCode() {
146       final int prime = 31;
147       int result = 1;
148       long temp;
149       temp = Double.doubleToLongBits(doubleFieldAsc);
150       result = prime * result + (int) (temp ^ (temp >>> 32));
151       result = prime * result + intFieldAsc;
152       result = prime * result + ((stringFieldAsc == null) ? 0 : stringFieldAsc.hashCode());
153       return result;
154     }
155 
156     @Override
157     public boolean equals(Object obj) {
158       if (this == obj) {
159         return true;
160       }
161       if (obj == null) {
162         return false;
163       }
164       if (getClass() != obj.getClass()) {
165         return false;
166       }
167       Pojo1 other = (Pojo1) obj;
168       if (Double.doubleToLongBits(doubleFieldAsc) !=
169           Double.doubleToLongBits(other.doubleFieldAsc)) {
170         return false;
171       }
172       if (intFieldAsc != other.intFieldAsc) {
173         return false;
174       }
175       if (stringFieldAsc == null) {
176         return other.stringFieldAsc == null;
177       } else {
178         return stringFieldAsc.equals(other.stringFieldAsc);
179       }
180     }
181   }
182 
183   /**
184    * A simple object to serialize.
185    */
186   private static class Pojo2 implements Comparable<Pojo2> {
187     final byte[] byteField1Asc;
188     final byte[] byteField2Dsc;
189     final String stringFieldDsc;
190     final byte[] byteField3Dsc;
191     final transient String str;
192 
193     public Pojo2(Object... vals) {
194       byteField1Asc  = vals.length > 0 ? (byte[]) vals[0] : null;
195       byteField2Dsc  = vals.length > 1 ? (byte[]) vals[1] : null;
196       stringFieldDsc = vals.length > 2 ? (String) vals[2] : null;
197       byteField3Dsc  = vals.length > 3 ? (byte[]) vals[3] : null;
198       str = new StringBuilder()
199             .append("{ ")
200             .append(Bytes.toStringBinary(byteField1Asc)).append(", ")
201             .append(Bytes.toStringBinary(byteField2Dsc)).append(", ")
202             .append(null == stringFieldDsc ? "" : "\"")
203             .append(stringFieldDsc)
204             .append(null == stringFieldDsc ? "" : "\"").append(", ")
205             .append(Bytes.toStringBinary(byteField3Dsc))
206             .append(" }")
207             .toString();
208     }
209 
210     @Override
211     public String toString() {
212       return str;
213     }
214 
215     @Override
216     public int compareTo(Pojo2 o) {
217       int cmp = NULL_SAFE_BYTES_COMPARATOR.compare(byteField1Asc, o.byteField1Asc);
218       if (cmp != 0) {
219         return cmp;
220       }
221       cmp = -NULL_SAFE_BYTES_COMPARATOR.compare(byteField2Dsc, o.byteField2Dsc);
222       if (cmp != 0) {
223         return cmp;
224       }
225 
226       if (null == stringFieldDsc) {
227         cmp = 1;
228       } else if (null == o.stringFieldDsc) {
229         cmp = -1;
230       } else if (stringFieldDsc.equals(o.stringFieldDsc)) {
231         cmp = 0;
232       } else {
233         cmp = -stringFieldDsc.compareTo(o.stringFieldDsc);
234       }
235 
236       if (cmp != 0) {
237         return cmp;
238       }
239       return -NULL_SAFE_BYTES_COMPARATOR.compare(byteField3Dsc, o.byteField3Dsc);
240     }
241 
242     @Override
243     public int hashCode() {
244       final int prime = 31;
245       int result = 1;
246       result = prime * result + Arrays.hashCode(byteField1Asc);
247       result = prime * result + Arrays.hashCode(byteField2Dsc);
248       result = prime * result + Arrays.hashCode(byteField3Dsc);
249       result = prime * result + ((stringFieldDsc == null) ? 0 : stringFieldDsc.hashCode());
250       return result;
251     }
252 
253     @Override
254     public boolean equals(Object obj) {
255       if (this == obj) {
256         return true;
257       }
258       if (obj == null) {
259         return false;
260       }
261       if (getClass() != obj.getClass()) {
262         return false;
263       }
264       Pojo2 other = (Pojo2) obj;
265       if (!Arrays.equals(byteField1Asc, other.byteField1Asc)) {
266         return false;
267       }
268       if (!Arrays.equals(byteField2Dsc, other.byteField2Dsc)) {
269         return false;
270       }
271       if (!Arrays.equals(byteField3Dsc, other.byteField3Dsc)) {
272         return false;
273       }
274       if (stringFieldDsc == null) {
275         return other.stringFieldDsc == null;
276       } else {
277         return stringFieldDsc.equals(other.stringFieldDsc);
278       }
279     }
280   }
281 
282   /**
283    * A custom data type implementation specialized for {@link Pojo1}.
284    */
285   private static class SpecializedPojo1Type1 implements DataType<Pojo1> {
286     private static final RawStringTerminated stringField = new RawStringTerminated("/");
287     private static final RawInteger intField = new RawInteger();
288     private static final RawDouble doubleField = new RawDouble();
289 
290     /**
291      * The {@link Struct} equivalent of this type.
292      */
293     public static Struct getGeneric() {
294       return new StructBuilder().add(stringField).add(intField)
295         .add(doubleField).toStruct();
296     }
297 
298     @Override
299     public boolean isOrderPreserving() {
300       return true;
301     }
302 
303     @Override
304     public Order getOrder() {
305       return null;
306     }
307 
308     @Override
309     public boolean isNullable() {
310       return false;
311     }
312 
313     @Override
314     public boolean isSkippable() {
315       return true;
316     }
317 
318     @Override
319     public int encodedLength(Pojo1 val) {
320       return stringField.encodedLength(val.stringFieldAsc) +
321           intField.encodedLength(val.intFieldAsc) +
322           doubleField.encodedLength(val.doubleFieldAsc);
323     }
324 
325     @Override
326     public Class<Pojo1> encodedClass() {
327       return Pojo1.class;
328     }
329 
330     @Override
331     public int skip(PositionedByteRange src) {
332       int skipped = stringField.skip(src);
333       skipped += intField.skip(src);
334       skipped += doubleField.skip(src);
335       return skipped;
336     }
337 
338     @Override
339     public Pojo1 decode(PositionedByteRange src) {
340       Object[] ret = new Object[3];
341       ret[0] = stringField.decode(src);
342       ret[1] = intField.decode(src);
343       ret[2] = doubleField.decode(src);
344       return new Pojo1(ret);
345     }
346 
347     @Override
348     public int encode(PositionedByteRange dst, Pojo1 val) {
349       int written = stringField.encode(dst, val.stringFieldAsc);
350       written += intField.encode(dst, val.intFieldAsc);
351       written += doubleField.encode(dst, val.doubleFieldAsc);
352       return written;
353     }
354   }
355 
356   /**
357    * A custom data type implementation specialized for {@link Pojo2}.
358    */
359   private static class SpecializedPojo2Type1 implements DataType<Pojo2> {
360     private static RawBytesTerminated byteField1 = new RawBytesTerminated("/");
361     private static RawBytesTerminated byteField2 =
362         new RawBytesTerminated(Order.DESCENDING, "/");
363     private static RawStringTerminated stringField =
364         new RawStringTerminated(Order.DESCENDING, new byte[] { 0x00 });
365     private static RawBytes byteField3 = RawBytes.DESCENDING;
366 
367     /**
368      * The {@link Struct} equivalent of this type.
369      */
370     public static Struct getGeneric() {
371       return new StructBuilder().add(byteField1).add(byteField2)
372         .add(stringField).add(byteField3).toStruct();
373     }
374 
375     @Override
376     public boolean isOrderPreserving() {
377       return true;
378     }
379 
380     @Override
381     public Order getOrder() {
382       return null;
383     }
384 
385     @Override
386     public boolean isNullable() {
387       return false;
388     }
389 
390     @Override
391     public boolean isSkippable() {
392       return true;
393     }
394 
395     @Override
396     public int encodedLength(Pojo2 val) {
397       return byteField1.encodedLength(val.byteField1Asc) +
398           byteField2.encodedLength(val.byteField2Dsc) +
399           stringField.encodedLength(val.stringFieldDsc) +
400           byteField3.encodedLength(val.byteField3Dsc);
401     }
402 
403     @Override
404     public Class<Pojo2> encodedClass() {
405       return Pojo2.class;
406     }
407 
408     @Override
409     public int skip(PositionedByteRange src) {
410       int skipped = byteField1.skip(src);
411       skipped += byteField2.skip(src);
412       skipped += stringField.skip(src);
413       skipped += byteField3.skip(src);
414       return skipped;
415     }
416 
417     @Override
418     public Pojo2 decode(PositionedByteRange src) {
419       Object[] ret = new Object[4];
420       ret[0] = byteField1.decode(src);
421       ret[1] = byteField2.decode(src);
422       ret[2] = stringField.decode(src);
423       ret[3] = byteField3.decode(src);
424       return new Pojo2(ret);
425     }
426 
427     @Override
428     public int encode(PositionedByteRange dst, Pojo2 val) {
429       int written = byteField1.encode(dst, val.byteField1Asc);
430       written += byteField2.encode(dst, val.byteField2Dsc);
431       written += stringField.encode(dst, val.stringFieldDsc);
432       written += byteField3.encode(dst, val.byteField3Dsc);
433       return written;
434     }
435   }
436 
437   @Test
438   @SuppressWarnings("unchecked")
439   public void testOrderPreservation() throws Exception {
440     Object[] vals = new Object[constructorArgs.length];
441     PositionedByteRange[] encodedGeneric = new PositionedByteRange[constructorArgs.length];
442     PositionedByteRange[] encodedSpecialized = new PositionedByteRange[constructorArgs.length];
443     Constructor<?> ctor = specialized.encodedClass().getConstructor(Object[].class);
444     for (int i = 0; i < vals.length; i++) {
445       vals[i] = ctor.newInstance(new Object[] { constructorArgs[i] });
446       encodedGeneric[i] = new SimplePositionedMutableByteRange(
447           generic.encodedLength(constructorArgs[i]));
448       encodedSpecialized[i] = new SimplePositionedMutableByteRange(
449           specialized.encodedLength(vals[i]));
450     }
451 
452     // populate our arrays
453     for (int i = 0; i < vals.length; i++) {
454       generic.encode(encodedGeneric[i], constructorArgs[i]);
455       encodedGeneric[i].setPosition(0);
456       specialized.encode(encodedSpecialized[i], vals[i]);
457       encodedSpecialized[i].setPosition(0);
458       assertArrayEquals(encodedGeneric[i].getBytes(), encodedSpecialized[i].getBytes());
459     }
460 
461     Arrays.sort(vals);
462     Arrays.sort(encodedGeneric);
463     Arrays.sort(encodedSpecialized);
464 
465     for (int i = 0; i < vals.length; i++) {
466       assertEquals(
467         "Struct encoder does not preserve sort order at position " + i,
468         vals[i],
469         ctor.newInstance(new Object[] { generic.decode(encodedGeneric[i]) }));
470       assertEquals(
471         "Specialized encoder does not preserve sort order at position " + i,
472         vals[i], specialized.decode(encodedSpecialized[i]));
473     }
474   }
475 }