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  
49    private Struct generic;
50    @SuppressWarnings("rawtypes")
51    private DataType specialized;
52    private Object[][] constructorArgs;
53  
54    public TestStruct(Struct generic, @SuppressWarnings("rawtypes") DataType specialized,
55        Object[][] constructorArgs) {
56      this.generic = generic;
57      this.specialized = specialized;
58      this.constructorArgs = constructorArgs;
59    }
60  
61    @Parameters
62    public static Collection<Object[]> params() {
63      Object[][] pojo1Args = {
64          new Object[] { "foo", 5,   10.001 },
65          new Object[] { "foo", 100, 7.0    },
66          new Object[] { "foo", 100, 10.001 },
67          new Object[] { "bar", 5,   10.001 },
68          new Object[] { "bar", 100, 10.001 },
69          new Object[] { "baz", 5,   10.001 },
70      };
71  
72      Object[][] pojo2Args = {
73        new Object[] { new byte[0], Bytes.toBytes("it"), "was", Bytes.toBytes("the") },
74        new Object[] { Bytes.toBytes("best"), new byte[0], "of", Bytes.toBytes("times,") },
75        new Object[] { Bytes.toBytes("it"), Bytes.toBytes("was"), "", Bytes.toBytes("the") },
76        new Object[] { Bytes.toBytes("worst"), Bytes.toBytes("of"), "times,", new byte[0] },
77        new Object[] { new byte[0], new byte[0], "", new byte[0] },
78      };
79  
80      Object[][] params = new Object[][] {
81          { SpecializedPojo1Type1.GENERIC, new SpecializedPojo1Type1(), pojo1Args },
82          { SpecializedPojo2Type1.GENERIC, new SpecializedPojo2Type1(), pojo2Args },
83      };
84      return Arrays.asList(params);
85    }
86  
87    static final Comparator<byte[]> NULL_SAFE_BYTES_COMPARATOR =
88        new Comparator<byte[]>() {
89          @Override
90          public int compare(byte[] o1, byte[] o2) {
91            if (o1 == o2) return 0;
92            if (null == o1) return -1;
93            if (null == o2) return 1;
94            return Bytes.compareTo(o1, o2);
95          }
96        };
97  
98    /**
99     * A simple object to serialize.
100    */
101   private static class Pojo1 implements Comparable<Pojo1> {
102     final String stringFieldAsc;
103     final int intFieldAsc;
104     final double doubleFieldAsc;
105     final transient String str;
106 
107     public Pojo1(Object... argv) {
108       stringFieldAsc = (String) argv[0];
109       intFieldAsc = (Integer) argv[1];
110       doubleFieldAsc = (Double) argv[2];
111       str = new StringBuilder()
112             .append("{ ")
113             .append(null == stringFieldAsc ? "" : "\"")
114             .append(stringFieldAsc)
115             .append(null == stringFieldAsc ? "" : "\"").append(", ")
116             .append(intFieldAsc).append(", ")
117             .append(doubleFieldAsc)
118             .append(" }")
119             .toString();
120     }
121 
122     @Override
123     public String toString() {
124       return str;
125     }
126 
127     @Override
128     public int compareTo(Pojo1 o) {
129       int cmp = stringFieldAsc.compareTo(o.stringFieldAsc);
130       if (cmp != 0) {
131         return cmp;
132       }
133       cmp = Integer.valueOf(intFieldAsc).compareTo(Integer.valueOf(o.intFieldAsc));
134       if (cmp != 0) {
135         return cmp;
136       }
137       return Double.compare(doubleFieldAsc, o.doubleFieldAsc);
138     }
139 
140     @Override
141     public int hashCode() {
142       final int prime = 31;
143       int result = 1;
144       long temp;
145       temp = Double.doubleToLongBits(doubleFieldAsc);
146       result = prime * result + (int) (temp ^ (temp >>> 32));
147       result = prime * result + intFieldAsc;
148       result = prime * result + ((stringFieldAsc == null) ? 0 : stringFieldAsc.hashCode());
149       return result;
150     }
151 
152     @Override
153     public boolean equals(Object obj) {
154       if (this == obj) {
155         return true;
156       }
157       if (obj == null) {
158         return false;
159       }
160       if (getClass() != obj.getClass()) {
161         return false;
162       }
163       Pojo1 other = (Pojo1) obj;
164       if (Double.doubleToLongBits(doubleFieldAsc) != Double.doubleToLongBits(other.doubleFieldAsc)) {
165         return false;
166       }
167       if (intFieldAsc != other.intFieldAsc) {
168         return false;
169       }
170       if (stringFieldAsc == null) {
171         if (other.stringFieldAsc != null) {
172           return false;
173         }
174       } else if (!stringFieldAsc.equals(other.stringFieldAsc)) {
175         return false;
176       }
177       return true;
178     }
179   }
180 
181   /**
182    * A simple object to serialize.
183    */
184   private static class Pojo2 implements Comparable<Pojo2> {
185     final byte[] byteField1Asc;
186     final byte[] byteField2Dsc;
187     final String stringFieldDsc;
188     final byte[] byteField3Dsc;
189     final transient String str;
190 
191     public Pojo2(Object... vals) {
192       byteField1Asc  = vals.length > 0 ? (byte[]) vals[0] : null;
193       byteField2Dsc  = vals.length > 1 ? (byte[]) vals[1] : null;
194       stringFieldDsc = vals.length > 2 ? (String) vals[2] : null;
195       byteField3Dsc  = vals.length > 3 ? (byte[]) vals[3] : null;
196       str = new StringBuilder()
197             .append("{ ")
198             .append(Bytes.toStringBinary(byteField1Asc)).append(", ")
199             .append(Bytes.toStringBinary(byteField2Dsc)).append(", ")
200             .append(null == stringFieldDsc ? "" : "\"")
201             .append(stringFieldDsc)
202             .append(null == stringFieldDsc ? "" : "\"").append(", ")
203             .append(Bytes.toStringBinary(byteField3Dsc))
204             .append(" }")
205             .toString();
206     }
207 
208     @Override
209     public String toString() {
210       return str;
211     }
212 
213     @Override
214     public int compareTo(Pojo2 o) {
215       int cmp = NULL_SAFE_BYTES_COMPARATOR.compare(byteField1Asc, o.byteField1Asc);
216       if (cmp != 0) {
217         return cmp;
218       }
219       cmp = -NULL_SAFE_BYTES_COMPARATOR.compare(byteField2Dsc, o.byteField2Dsc);
220       if (cmp != 0) {
221         return cmp;
222       }
223       if (null == stringFieldDsc) {
224         cmp = 1;
225       }
226       else if (null == o.stringFieldDsc) {
227         cmp = -1;
228       }
229       else if (stringFieldDsc.equals(o.stringFieldDsc)) {
230         cmp = 0;
231       }
232       else cmp = -stringFieldDsc.compareTo(o.stringFieldDsc);
233       if (cmp != 0) {
234         return cmp;
235       }
236       return -NULL_SAFE_BYTES_COMPARATOR.compare(byteField3Dsc, o.byteField3Dsc);
237     }
238 
239     @Override
240     public int hashCode() {
241       final int prime = 31;
242       int result = 1;
243       result = prime * result + Arrays.hashCode(byteField1Asc);
244       result = prime * result + Arrays.hashCode(byteField2Dsc);
245       result = prime * result + Arrays.hashCode(byteField3Dsc);
246       result = prime * result + ((stringFieldDsc == null) ? 0 : stringFieldDsc.hashCode());
247       return result;
248     }
249 
250     @Override
251     public boolean equals(Object obj) {
252       if (this == obj) {
253         return true;
254       }
255       if (obj == null) {
256         return false;
257       }
258       if (getClass() != obj.getClass()) {
259         return false;
260       }
261       Pojo2 other = (Pojo2) obj;
262       if (!Arrays.equals(byteField1Asc, other.byteField1Asc)) {
263         return false;
264       }
265       if (!Arrays.equals(byteField2Dsc, other.byteField2Dsc)) {
266         return false;
267       }
268       if (!Arrays.equals(byteField3Dsc, other.byteField3Dsc)) {
269         return false;
270       }
271       if (stringFieldDsc == null) {
272         if (other.stringFieldDsc != null) {
273           return false;
274         }
275       } else if (!stringFieldDsc.equals(other.stringFieldDsc)) {
276         return false;
277       }
278       return true;
279     }
280   }
281 
282   /**
283    * A custom data type implementation specialized for {@link Pojo1}.
284    */
285   private static class SpecializedPojo1Type1 implements DataType<Pojo1> {
286 
287     private static final RawStringTerminated stringField = new RawStringTerminated("/");
288     private static final RawInteger intField = new RawInteger();
289     private static final RawDouble doubleField = new RawDouble();
290 
291     /**
292      * The {@link Struct} equivalent of this type.
293      */
294     public static Struct GENERIC =
295         new StructBuilder().add(stringField)
296                            .add(intField)
297                            .add(doubleField)
298                            .toStruct();
299 
300     @Override
301     public boolean isOrderPreserving() { return true; }
302 
303     @Override
304     public Order getOrder() { return null; }
305 
306     @Override
307     public boolean isNullable() { return false; }
308 
309     @Override
310     public boolean isSkippable() { return true; }
311 
312     @Override
313     public int encodedLength(Pojo1 val) {
314       return
315           stringField.encodedLength(val.stringFieldAsc) +
316           intField.encodedLength(val.intFieldAsc) +
317           doubleField.encodedLength(val.doubleFieldAsc);
318     }
319 
320     @Override
321     public Class<Pojo1> encodedClass() { return Pojo1.class; }
322 
323     @Override
324     public int skip(PositionedByteRange src) {
325       int skipped = stringField.skip(src);
326       skipped += intField.skip(src);
327       skipped += doubleField.skip(src);
328       return skipped;
329     }
330 
331     @Override
332     public Pojo1 decode(PositionedByteRange src) {
333       Object[] ret = new Object[3];
334       ret[0] = stringField.decode(src);
335       ret[1] = intField.decode(src);
336       ret[2] = doubleField.decode(src);
337       return new Pojo1(ret);
338     }
339 
340     @Override
341     public int encode(PositionedByteRange dst, Pojo1 val) {
342       int written = stringField.encode(dst, val.stringFieldAsc);
343       written += intField.encode(dst, val.intFieldAsc);
344       written += doubleField.encode(dst, val.doubleFieldAsc);
345       return written;
346     }
347   }
348 
349   /**
350    * A custom data type implementation specialized for {@link Pojo2}.
351    */
352   private static class SpecializedPojo2Type1 implements DataType<Pojo2> {
353 
354     private static RawBytesTerminated byteField1 = new RawBytesTerminated("/");
355     private static RawBytesTerminated byteField2 =
356         new RawBytesTerminated(Order.DESCENDING, "/");
357     private static RawStringTerminated stringField =
358         new RawStringTerminated(Order.DESCENDING, new byte[] { 0x00 });
359     private static RawBytes byteField3 = RawBytes.DESCENDING;
360 
361     /**
362      * The {@link Struct} equivalent of this type.
363      */
364     public static Struct GENERIC =
365         new StructBuilder().add(byteField1)
366                            .add(byteField2)
367                            .add(stringField)
368                            .add(byteField3)
369                            .toStruct();
370 
371     @Override
372     public boolean isOrderPreserving() { return true; }
373 
374     @Override
375     public Order getOrder() { return null; }
376 
377     @Override
378     public boolean isNullable() { return false; }
379 
380     @Override
381     public boolean isSkippable() { return true; }
382 
383     @Override
384     public int encodedLength(Pojo2 val) {
385       return
386           byteField1.encodedLength(val.byteField1Asc) +
387           byteField2.encodedLength(val.byteField2Dsc) +
388           stringField.encodedLength(val.stringFieldDsc) +
389           byteField3.encodedLength(val.byteField3Dsc);
390     }
391 
392     @Override
393     public Class<Pojo2> encodedClass() { return Pojo2.class; }
394 
395     @Override
396     public int skip(PositionedByteRange src) {
397       int skipped = byteField1.skip(src);
398       skipped += byteField2.skip(src);
399       skipped += stringField.skip(src);
400       skipped += byteField3.skip(src);
401       return skipped;
402     }
403 
404     @Override
405     public Pojo2 decode(PositionedByteRange src) {
406       Object[] ret = new Object[4];
407       ret[0] = byteField1.decode(src);
408       ret[1] = byteField2.decode(src);
409       ret[2] = stringField.decode(src);
410       ret[3] = byteField3.decode(src);
411       return new Pojo2(ret);
412     }
413 
414     @Override
415     public int encode(PositionedByteRange dst, Pojo2 val) {
416       int written = byteField1.encode(dst, val.byteField1Asc);
417       written += byteField2.encode(dst, val.byteField2Dsc);
418       written += stringField.encode(dst, val.stringFieldDsc);
419       written += byteField3.encode(dst, val.byteField3Dsc);
420       return written;
421     }
422   }
423 
424   @Test
425   @SuppressWarnings("unchecked")
426   public void testOrderPreservation() throws Exception {
427     Object[] vals = new Object[constructorArgs.length];
428     PositionedByteRange[] encodedGeneric = new PositionedByteRange[constructorArgs.length];
429     PositionedByteRange[] encodedSpecialized = new PositionedByteRange[constructorArgs.length];
430     Constructor<?> ctor = specialized.encodedClass().getConstructor(Object[].class);
431     for (int i = 0; i < vals.length; i++) {
432       vals[i] = ctor.newInstance(new Object[] { constructorArgs[i] });
433       encodedGeneric[i] = new SimplePositionedMutableByteRange(
434           generic.encodedLength(constructorArgs[i]));
435       encodedSpecialized[i] = new SimplePositionedMutableByteRange(
436           specialized.encodedLength(vals[i]));
437     }
438 
439     // populate our arrays
440     for (int i = 0; i < vals.length; i++) {
441       generic.encode(encodedGeneric[i], constructorArgs[i]);
442       encodedGeneric[i].setPosition(0);
443       specialized.encode(encodedSpecialized[i], vals[i]);
444       encodedSpecialized[i].setPosition(0);
445       assertArrayEquals(encodedGeneric[i].getBytes(), encodedSpecialized[i].getBytes());
446     }
447 
448     Arrays.sort(vals);
449     Arrays.sort(encodedGeneric);
450     Arrays.sort(encodedSpecialized);
451 
452     for (int i = 0; i < vals.length; i++) {
453       assertEquals(
454         "Struct encoder does not preserve sort order at position " + i,
455         vals[i],
456         ctor.newInstance(new Object[] { generic.decode(encodedGeneric[i]) }));
457       assertEquals(
458         "Specialized encoder does not preserve sort order at position " + i,
459         vals[i], specialized.decode(encodedSpecialized[i]));
460     }
461   }
462 }