001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase.types; 019 020import static org.junit.Assert.assertArrayEquals; 021import static org.junit.Assert.assertEquals; 022 023import java.lang.reflect.Constructor; 024import java.util.Arrays; 025import java.util.Collection; 026import java.util.Comparator; 027import org.apache.hadoop.hbase.HBaseClassTestRule; 028import org.apache.hadoop.hbase.testclassification.MiscTests; 029import org.apache.hadoop.hbase.testclassification.SmallTests; 030import org.apache.hadoop.hbase.util.Bytes; 031import org.apache.hadoop.hbase.util.Order; 032import org.apache.hadoop.hbase.util.PositionedByteRange; 033import org.apache.hadoop.hbase.util.SimplePositionedMutableByteRange; 034import org.junit.ClassRule; 035import org.junit.Test; 036import org.junit.experimental.categories.Category; 037import org.junit.runner.RunWith; 038import org.junit.runners.Parameterized; 039import org.junit.runners.Parameterized.Parameters; 040 041/** 042 * This class both tests and demonstrates how to construct compound rowkeys 043 * from a POJO. The code under test is {@link Struct}. 044 * {@link SpecializedPojo1Type1} demonstrates how one might create their own 045 * custom data type extension for an application POJO. 046 */ 047@RunWith(Parameterized.class) 048@Category({MiscTests.class, SmallTests.class}) 049public class TestStruct { 050 051 @ClassRule 052 public static final HBaseClassTestRule CLASS_RULE = 053 HBaseClassTestRule.forClass(TestStruct.class); 054 055 @Parameterized.Parameter(value = 0) 056 public Struct generic; 057 058 @SuppressWarnings("rawtypes") 059 @Parameterized.Parameter(value = 1) 060 public DataType specialized; 061 062 @Parameterized.Parameter(value = 2) 063 public Object[][] constructorArgs; 064 065 @Parameters 066 public static Collection<Object[]> params() { 067 Object[][] pojo1Args = { 068 new Object[] { "foo", 5, 10.001 }, 069 new Object[] { "foo", 100, 7.0 }, 070 new Object[] { "foo", 100, 10.001 }, 071 new Object[] { "bar", 5, 10.001 }, 072 new Object[] { "bar", 100, 10.001 }, 073 new Object[] { "baz", 5, 10.001 }, 074 }; 075 076 Object[][] pojo2Args = { 077 new Object[] { new byte[0], Bytes.toBytes("it"), "was", Bytes.toBytes("the") }, 078 new Object[] { Bytes.toBytes("best"), new byte[0], "of", Bytes.toBytes("times,") }, 079 new Object[] { Bytes.toBytes("it"), Bytes.toBytes("was"), "", Bytes.toBytes("the") }, 080 new Object[] { Bytes.toBytes("worst"), Bytes.toBytes("of"), "times,", new byte[0] }, 081 new Object[] { new byte[0], new byte[0], "", new byte[0] }, 082 }; 083 084 Object[][] params = new Object[][] { 085 { SpecializedPojo1Type1.GENERIC, new SpecializedPojo1Type1(), pojo1Args }, 086 { SpecializedPojo2Type1.GENERIC, new SpecializedPojo2Type1(), pojo2Args }, 087 }; 088 return Arrays.asList(params); 089 } 090 091 static final Comparator<byte[]> NULL_SAFE_BYTES_COMPARATOR = 092 new Comparator<byte[]>() { 093 @Override 094 public int compare(byte[] o1, byte[] o2) { 095 if (o1 == o2) return 0; 096 if (null == o1) return -1; 097 if (null == o2) return 1; 098 return Bytes.compareTo(o1, o2); 099 } 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 if (other.stringFieldAsc != null) { 177 return false; 178 } 179 } else if (!stringFieldAsc.equals(other.stringFieldAsc)) { 180 return false; 181 } 182 return true; 183 } 184 } 185 186 /** 187 * A simple object to serialize. 188 */ 189 private static class Pojo2 implements Comparable<Pojo2> { 190 final byte[] byteField1Asc; 191 final byte[] byteField2Dsc; 192 final String stringFieldDsc; 193 final byte[] byteField3Dsc; 194 final transient String str; 195 196 public Pojo2(Object... vals) { 197 byteField1Asc = vals.length > 0 ? (byte[]) vals[0] : null; 198 byteField2Dsc = vals.length > 1 ? (byte[]) vals[1] : null; 199 stringFieldDsc = vals.length > 2 ? (String) vals[2] : null; 200 byteField3Dsc = vals.length > 3 ? (byte[]) vals[3] : null; 201 str = new StringBuilder() 202 .append("{ ") 203 .append(Bytes.toStringBinary(byteField1Asc)).append(", ") 204 .append(Bytes.toStringBinary(byteField2Dsc)).append(", ") 205 .append(null == stringFieldDsc ? "" : "\"") 206 .append(stringFieldDsc) 207 .append(null == stringFieldDsc ? "" : "\"").append(", ") 208 .append(Bytes.toStringBinary(byteField3Dsc)) 209 .append(" }") 210 .toString(); 211 } 212 213 @Override 214 public String toString() { 215 return str; 216 } 217 218 @Override 219 public int compareTo(Pojo2 o) { 220 int cmp = NULL_SAFE_BYTES_COMPARATOR.compare(byteField1Asc, o.byteField1Asc); 221 if (cmp != 0) { 222 return cmp; 223 } 224 cmp = -NULL_SAFE_BYTES_COMPARATOR.compare(byteField2Dsc, o.byteField2Dsc); 225 if (cmp != 0) { 226 return cmp; 227 } 228 if (null == stringFieldDsc) { 229 cmp = 1; 230 } 231 else if (null == o.stringFieldDsc) { 232 cmp = -1; 233 } 234 else if (stringFieldDsc.equals(o.stringFieldDsc)) { 235 cmp = 0; 236 } 237 else cmp = -stringFieldDsc.compareTo(o.stringFieldDsc); 238 if (cmp != 0) { 239 return cmp; 240 } 241 return -NULL_SAFE_BYTES_COMPARATOR.compare(byteField3Dsc, o.byteField3Dsc); 242 } 243 244 @Override 245 public int hashCode() { 246 final int prime = 31; 247 int result = 1; 248 result = prime * result + Arrays.hashCode(byteField1Asc); 249 result = prime * result + Arrays.hashCode(byteField2Dsc); 250 result = prime * result + Arrays.hashCode(byteField3Dsc); 251 result = prime * result + ((stringFieldDsc == null) ? 0 : stringFieldDsc.hashCode()); 252 return result; 253 } 254 255 @Override 256 public boolean equals(Object obj) { 257 if (this == obj) { 258 return true; 259 } 260 if (obj == null) { 261 return false; 262 } 263 if (getClass() != obj.getClass()) { 264 return false; 265 } 266 Pojo2 other = (Pojo2) obj; 267 if (!Arrays.equals(byteField1Asc, other.byteField1Asc)) { 268 return false; 269 } 270 if (!Arrays.equals(byteField2Dsc, other.byteField2Dsc)) { 271 return false; 272 } 273 if (!Arrays.equals(byteField3Dsc, other.byteField3Dsc)) { 274 return false; 275 } 276 if (stringFieldDsc == null) { 277 if (other.stringFieldDsc != null) { 278 return false; 279 } 280 } else if (!stringFieldDsc.equals(other.stringFieldDsc)) { 281 return false; 282 } 283 return true; 284 } 285 } 286 287 /** 288 * A custom data type implementation specialized for {@link Pojo1}. 289 */ 290 private static class SpecializedPojo1Type1 implements DataType<Pojo1> { 291 292 private static final RawStringTerminated stringField = new RawStringTerminated("/"); 293 private static final RawInteger intField = new RawInteger(); 294 private static final RawDouble doubleField = new RawDouble(); 295 296 /** 297 * The {@link Struct} equivalent of this type. 298 */ 299 public static Struct GENERIC = 300 new StructBuilder().add(stringField) 301 .add(intField) 302 .add(doubleField) 303 .toStruct(); 304 305 @Override 306 public boolean isOrderPreserving() { return true; } 307 308 @Override 309 public Order getOrder() { return null; } 310 311 @Override 312 public boolean isNullable() { return false; } 313 314 @Override 315 public boolean isSkippable() { return true; } 316 317 @Override 318 public int encodedLength(Pojo1 val) { 319 return 320 stringField.encodedLength(val.stringFieldAsc) + 321 intField.encodedLength(val.intFieldAsc) + 322 doubleField.encodedLength(val.doubleFieldAsc); 323 } 324 325 @Override 326 public Class<Pojo1> encodedClass() { return Pojo1.class; } 327 328 @Override 329 public int skip(PositionedByteRange src) { 330 int skipped = stringField.skip(src); 331 skipped += intField.skip(src); 332 skipped += doubleField.skip(src); 333 return skipped; 334 } 335 336 @Override 337 public Pojo1 decode(PositionedByteRange src) { 338 Object[] ret = new Object[3]; 339 ret[0] = stringField.decode(src); 340 ret[1] = intField.decode(src); 341 ret[2] = doubleField.decode(src); 342 return new Pojo1(ret); 343 } 344 345 @Override 346 public int encode(PositionedByteRange dst, Pojo1 val) { 347 int written = stringField.encode(dst, val.stringFieldAsc); 348 written += intField.encode(dst, val.intFieldAsc); 349 written += doubleField.encode(dst, val.doubleFieldAsc); 350 return written; 351 } 352 } 353 354 /** 355 * A custom data type implementation specialized for {@link Pojo2}. 356 */ 357 private static class SpecializedPojo2Type1 implements DataType<Pojo2> { 358 359 private static RawBytesTerminated byteField1 = new RawBytesTerminated("/"); 360 private static RawBytesTerminated byteField2 = 361 new RawBytesTerminated(Order.DESCENDING, "/"); 362 private static RawStringTerminated stringField = 363 new RawStringTerminated(Order.DESCENDING, new byte[] { 0x00 }); 364 private static RawBytes byteField3 = RawBytes.DESCENDING; 365 366 /** 367 * The {@link Struct} equivalent of this type. 368 */ 369 public static Struct GENERIC = 370 new StructBuilder().add(byteField1) 371 .add(byteField2) 372 .add(stringField) 373 .add(byteField3) 374 .toStruct(); 375 376 @Override 377 public boolean isOrderPreserving() { return true; } 378 379 @Override 380 public Order getOrder() { return null; } 381 382 @Override 383 public boolean isNullable() { return false; } 384 385 @Override 386 public boolean isSkippable() { return true; } 387 388 @Override 389 public int encodedLength(Pojo2 val) { 390 return 391 byteField1.encodedLength(val.byteField1Asc) + 392 byteField2.encodedLength(val.byteField2Dsc) + 393 stringField.encodedLength(val.stringFieldDsc) + 394 byteField3.encodedLength(val.byteField3Dsc); 395 } 396 397 @Override 398 public Class<Pojo2> encodedClass() { return Pojo2.class; } 399 400 @Override 401 public int skip(PositionedByteRange src) { 402 int skipped = byteField1.skip(src); 403 skipped += byteField2.skip(src); 404 skipped += stringField.skip(src); 405 skipped += byteField3.skip(src); 406 return skipped; 407 } 408 409 @Override 410 public Pojo2 decode(PositionedByteRange src) { 411 Object[] ret = new Object[4]; 412 ret[0] = byteField1.decode(src); 413 ret[1] = byteField2.decode(src); 414 ret[2] = stringField.decode(src); 415 ret[3] = byteField3.decode(src); 416 return new Pojo2(ret); 417 } 418 419 @Override 420 public int encode(PositionedByteRange dst, Pojo2 val) { 421 int written = byteField1.encode(dst, val.byteField1Asc); 422 written += byteField2.encode(dst, val.byteField2Dsc); 423 written += stringField.encode(dst, val.stringFieldDsc); 424 written += byteField3.encode(dst, val.byteField3Dsc); 425 return written; 426 } 427 } 428 429 @Test 430 @SuppressWarnings("unchecked") 431 public void testOrderPreservation() throws Exception { 432 Object[] vals = new Object[constructorArgs.length]; 433 PositionedByteRange[] encodedGeneric = new PositionedByteRange[constructorArgs.length]; 434 PositionedByteRange[] encodedSpecialized = new PositionedByteRange[constructorArgs.length]; 435 Constructor<?> ctor = specialized.encodedClass().getConstructor(Object[].class); 436 for (int i = 0; i < vals.length; i++) { 437 vals[i] = ctor.newInstance(new Object[] { constructorArgs[i] }); 438 encodedGeneric[i] = new SimplePositionedMutableByteRange( 439 generic.encodedLength(constructorArgs[i])); 440 encodedSpecialized[i] = new SimplePositionedMutableByteRange( 441 specialized.encodedLength(vals[i])); 442 } 443 444 // populate our arrays 445 for (int i = 0; i < vals.length; i++) { 446 generic.encode(encodedGeneric[i], constructorArgs[i]); 447 encodedGeneric[i].setPosition(0); 448 specialized.encode(encodedSpecialized[i], vals[i]); 449 encodedSpecialized[i].setPosition(0); 450 assertArrayEquals(encodedGeneric[i].getBytes(), encodedSpecialized[i].getBytes()); 451 } 452 453 Arrays.sort(vals); 454 Arrays.sort(encodedGeneric); 455 Arrays.sort(encodedSpecialized); 456 457 for (int i = 0; i < vals.length; i++) { 458 assertEquals( 459 "Struct encoder does not preserve sort order at position " + i, 460 vals[i], 461 ctor.newInstance(new Object[] { generic.decode(encodedGeneric[i]) })); 462 assertEquals( 463 "Specialized encoder does not preserve sort order at position " + i, 464 vals[i], specialized.decode(encodedSpecialized[i])); 465 } 466 } 467}