/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.hbase.types;
import java.util.Iterator;
import org.apache.hadoop.hbase.classification.InterfaceAudience;
import org.apache.hadoop.hbase.util.Order;
import org.apache.hadoop.hbase.util.PositionedByteRange;
/**
* <p>
* {@code Struct} is a simple {@link DataType} for implementing "compound
* rowkey" and "compound qualifier" schema design strategies.
* </p>
* <h3>Encoding</h3>
* <p>
* {@code Struct} member values are encoded onto the target byte[] in the order
* in which they are declared. A {@code Struct} may be used as a member of
* another {@code Struct}. {@code Struct}s are not {@code nullable} but their
* component fields may be.
* </p>
* <h3>Trailing Nulls</h3>
* <p>
* {@code Struct} treats the right-most nullable field members as special.
* Rather than writing null values to the output buffer, {@code Struct} omits
* those records all together. When reading back a value, it will look for the
* scenario where the end of the buffer has been reached but there are still
* nullable fields remaining in the {@code Struct} definition. When this
* happens, it will produce null entries for the remaining values. For example:
* </p>
* <pre>
* StructBuilder builder = new StructBuilder()
* .add(OrderedNumeric.ASCENDING) // nullable
* .add(OrderedString.ASCENDING) // nullable
* Struct shorter = builder.toStruct();
* Struct longer = builder.add(OrderedNumeric.ASCENDING) // nullable
* .toStruct();
*
* PositionedByteRange buf1 = new SimplePositionedByteRange(7);
* PositionedByteRange buf2 = new SimplePositionedByteRange(7);
* Object[] val = new Object[] { BigDecimal.ONE, "foo" };
* shorter.encode(buf1, val); // write short value with short Struct
* buf1.setPosition(0); // reset position marker, prepare for read
* longer.decode(buf1); // => { BigDecimal.ONE, "foo", null } ; long Struct reads implied null
* longer.encode(buf2, val); // write short value with long struct
* Bytes.equals(buf1.getBytes(), buf2.getBytes()); // => true; long Struct skips writing null
* </pre>
* <h3>Sort Order</h3>
* <p>
* {@code Struct} instances sort according to the composite order of their
* fields, that is, left-to-right and depth-first. This can also be thought of
* as lexicographic comparison of concatenated members.
* </p>
* <p>
* {@link StructIterator} is provided as a convenience for consuming the
* sequence of values. Users may find it more appropriate to provide their own
* custom {@link DataType} for encoding application objects rather than using
* this {@code Object[]} implementation. Examples are provided in test.
* </p>
* @see StructIterator
* @see DataType#isNullable()
*/
@InterfaceAudience.Public
public class Struct implements DataType<Object[]> {
@SuppressWarnings("rawtypes")
protected final DataType[] fields;
protected final boolean isOrderPreserving;
protected final boolean isSkippable;
/**
* Create a new {@code Struct} instance defined as the sequence of
* {@code HDataType}s in {@code memberTypes}.
* <p>
* A {@code Struct} is {@code orderPreserving} when all of its fields
* are {@code orderPreserving}. A {@code Struct} is {@code skippable} when
* all of its fields are {@code skippable}.
* </p>
*/
@SuppressWarnings("rawtypes")
public Struct(DataType[] memberTypes) {
this.fields = memberTypes;
// a Struct is not orderPreserving when any of its fields are not.
boolean preservesOrder = true;
// a Struct is not skippable when any of its fields are not.
boolean skippable = true;
for (int i = 0; i < this.fields.length; i++) {
DataType dt = this.fields[i];
if (!dt.isOrderPreserving()) preservesOrder = false;
if (i < this.fields.length - 2 && !dt.isSkippable()) {
throw new IllegalArgumentException("Field in position " + i
+ " is not skippable. Non-right-most struct fields must be skippable.");
}
if (!dt.isSkippable()) skippable = false;
}
this.isOrderPreserving = preservesOrder;
this.isSkippable = skippable;
}
@Override
public boolean isOrderPreserving() { return isOrderPreserving; }
@Override
public Order getOrder() { return null; }
@Override
public boolean isNullable() { return false; }
@Override
public boolean isSkippable() { return isSkippable; }
@SuppressWarnings("unchecked")
@Override
public int encodedLength(Object[] val) {
assert fields.length >= val.length;
int sum = 0;
for (int i = 0; i < val.length; i++)
sum += fields[i].encodedLength(val[i]);
return sum;
}
@Override
public Class<Object[]> encodedClass() { return Object[].class; }
/**
* Retrieve an {@link Iterator} over the values encoded in {@code src}.
* {@code src}'s position is consumed by consuming this iterator.
*/
public StructIterator iterator(PositionedByteRange src) {
return new StructIterator(src, fields);
}
@Override
public int skip(PositionedByteRange src) {
StructIterator it = iterator(src);
int skipped = 0;
while (it.hasNext())
skipped += it.skip();
return skipped;
}
@Override
public Object[] decode(PositionedByteRange src) {
int i = 0;
Object[] ret = new Object[fields.length];
Iterator<Object> it = iterator(src);
while (it.hasNext())
ret[i++] = it.next();
return ret;
}
/**
* Read the field at {@code index}. {@code src}'s position is not affected.
*/
public Object decode(PositionedByteRange src, int index) {
assert index >= 0;
StructIterator it = iterator(src.shallowCopy());
for (; index > 0; index--)
it.skip();
return it.next();
}
@SuppressWarnings("unchecked")
@Override
public int encode(PositionedByteRange dst, Object[] val) {
if (val.length == 0) return 0;
assert fields.length >= val.length;
int end, written = 0;
// find the last occurrence of a non-null or null and non-nullable value
for (end = val.length - 1; end > -1; end--) {
if (null != val[end] || (null == val[end] && !fields[end].isNullable())) break;
}
for (int i = 0; i <= end; i++) {
written += fields[i].encode(dst, val[i]);
}
return written;
}
}