diff --git a/src/main/java/com/addthis/bundle/core/Bundle.java b/src/main/java/com/addthis/bundle/core/Bundle.java index ed61971..8d7b1cf 100644 --- a/src/main/java/com/addthis/bundle/core/Bundle.java +++ b/src/main/java/com/addthis/bundle/core/Bundle.java @@ -21,7 +21,7 @@ * multiple fields. Iterator returns a list of fields with bound values. * Field iterators MUST obey the same constraints as the getFormat() call. */ -public interface Bundle extends Iterable, BundleFormatted, BundleFactory { +public interface Bundle extends Iterable, BundleFormatted, BundleFactory, IndexBundle { /** * gets the specified field as a String value. diff --git a/src/main/java/com/addthis/bundle/core/BundleComparator.java b/src/main/java/com/addthis/bundle/core/BundleComparator.java index ac8e4bd..5a5ec8b 100644 --- a/src/main/java/com/addthis/bundle/core/BundleComparator.java +++ b/src/main/java/com/addthis/bundle/core/BundleComparator.java @@ -15,6 +15,7 @@ import java.util.Comparator; +import com.addthis.bundle.value.ValueType; import com.addthis.codec.Codec; import com.addthis.bundle.value.ValueObject; @@ -75,9 +76,9 @@ public int compare(final Bundle left, final Bundle right) { if (vRight == null) { return asc ? -1 : 1; } - final ValueObject.TYPE tLeft = vLeft.getObjectType(); - final ValueObject.TYPE tRight = vRight.getObjectType(); - if (tLeft != tRight || tLeft == ValueObject.TYPE.STRING) { + final ValueType tLeft = vLeft.getObjectType(); + final ValueType tRight = vRight.getObjectType(); + if (tLeft != tRight || tLeft == ValueType.STRING) { final int val = vLeft.toString().compareTo(vRight.toString()); if (val != 0) { return asc ? val : -val; diff --git a/src/main/java/com/addthis/bundle/core/BundleFormat.java b/src/main/java/com/addthis/bundle/core/BundleFormat.java index f773bf2..61dffbe 100644 --- a/src/main/java/com/addthis/bundle/core/BundleFormat.java +++ b/src/main/java/com/addthis/bundle/core/BundleFormat.java @@ -20,7 +20,7 @@ * unless the underlying format has changed. A change in format object * is used by many stream clients to perform re-binding. */ -public interface BundleFormat extends Iterable { +public interface BundleFormat extends Iterable, IndexFormat { /** * returns a field for the given name. creates a new field @@ -41,11 +41,6 @@ public interface BundleFormat extends Iterable { */ public BundleField getField(int pos); - /** - * @return numnber of bound fields in this format - */ - public int getFieldCount(); - /** * @return an object that changes only when the format changes */ diff --git a/src/main/java/com/addthis/bundle/core/IndexBundle.java b/src/main/java/com/addthis/bundle/core/IndexBundle.java new file mode 100644 index 0000000..1819376 --- /dev/null +++ b/src/main/java/com/addthis/bundle/core/IndexBundle.java @@ -0,0 +1,44 @@ +/* + * Licensed 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 com.addthis.bundle.core; + +import com.addthis.bundle.value.ValueObject; + + +/** + * Represents one "line" or "packet" of data having multiple fields. + */ +public interface IndexBundle { + + /** + * gets the field at the given index as a ValueObject + */ + public ValueObject value(int index); + + /** + * sets the field as the given index to a ValueObject. + */ + public void value(int index, ValueObject value); + + /** + * returns the Format for the bundle. It has metadata + * about the columns in this bundle which varies by implementation. + * + * returns the format for this bundle. maps to known fields. the + * object returned by this call MUST NOT change unless the underlying + * stream or object has changed in an incompatible way. the order + * of fields presented by this object MUST remain consistent. + */ + public IndexFormat format(); +} diff --git a/src/main/java/com/addthis/bundle/core/IndexFormat.java b/src/main/java/com/addthis/bundle/core/IndexFormat.java new file mode 100644 index 0000000..d148cfc --- /dev/null +++ b/src/main/java/com/addthis/bundle/core/IndexFormat.java @@ -0,0 +1,25 @@ +/* + * Licensed 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 com.addthis.bundle.core; + + +/** + */ +public interface IndexFormat { + + /** + * @return numnber of fields in this format + */ + public int getFieldCount(); +} diff --git a/src/main/java/com/addthis/bundle/core/index/ArrayBundle.java b/src/main/java/com/addthis/bundle/core/index/ArrayBundle.java new file mode 100644 index 0000000..7d9078c --- /dev/null +++ b/src/main/java/com/addthis/bundle/core/index/ArrayBundle.java @@ -0,0 +1,51 @@ +/* + * Licensed 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 com.addthis.bundle.core.index; + +import java.util.Arrays; + +import com.addthis.bundle.core.IndexBundle; +import com.addthis.bundle.core.IndexFormat; +import com.addthis.bundle.value.ValueObject; + +public class ArrayBundle implements IndexBundle { + + final ValueObject[] valueArray; + final IndexFormat format; + + public ArrayBundle(IndexFormat format) { + this.format = format; + this.valueArray = new ValueObject[format.getFieldCount()]; + } + + @Override + public String toString() { + return Arrays.toString(valueArray); + } + + @Override + public ValueObject value(int index) { + return valueArray[index]; + } + + @Override + public void value(int index, ValueObject value) { + valueArray[index] = value; + } + + @Override + public IndexFormat format() { + return format; + } +} diff --git a/src/main/java/com/addthis/bundle/core/index/ExtIndexBundle.java b/src/main/java/com/addthis/bundle/core/index/ExtIndexBundle.java new file mode 100644 index 0000000..7b25ca8 --- /dev/null +++ b/src/main/java/com/addthis/bundle/core/index/ExtIndexBundle.java @@ -0,0 +1,61 @@ +/* + * Licensed 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 com.addthis.bundle.core.index; + +import com.addthis.bundle.core.IndexBundle; +import com.addthis.bundle.value.ValueArray; +import com.addthis.bundle.value.ValueCustom; +import com.addthis.bundle.value.ValueMap; +import com.addthis.bundle.value.ValueObject; +import com.addthis.bundle.value.ValueString; + +import io.netty.buffer.ByteBuf; + +/** + * Non-standard (not implemented or needed by 'Bundle') methods + * specific to a sub-set of IndexBundle implementations that provide + * convenience or alternate interfacing. + * + * These get/ set methods make it possible for implementations to + * be more flexible with their storage options, potentially avoid + * some wrapper object overhead, use different object types without + * interacting with standard bundles, and hopefully allow more concise + * and explicit downstream code. + */ +public interface ExtIndexBundle extends IndexBundle, Iterable { + + ValueString string(int index); + void string(int index, ValueString string); + + ValueArray array(int index); + void array(int index, ValueArray array); + + ValueMap map(int index); + void map(int index, ValueMap map); + + ByteBuf buf(int index); + void buf(int index, ByteBuf buf); + + T custom(int index); + void custom(int index, ValueCustom custom); + + long integer(int index); + void integer(int index, long integer); + + double floating(int index); + void floating(int index, double floating); + + @Override + public ExtIndexFormat format(); +} diff --git a/src/main/java/com/addthis/bundle/core/index/ExtIndexFormat.java b/src/main/java/com/addthis/bundle/core/index/ExtIndexFormat.java new file mode 100644 index 0000000..e30a7f6 --- /dev/null +++ b/src/main/java/com/addthis/bundle/core/index/ExtIndexFormat.java @@ -0,0 +1,42 @@ +/* + * Licensed 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 com.addthis.bundle.core.index; + + +import com.addthis.bundle.core.IndexFormat; +import com.addthis.bundle.value.ValueType; + +/** + * Non-standard (not implemented or needed by 'Bundle') methods + * specific to a sub-set of IndexBundle implementations that provide + * convenience or alternate interfacing. + */ +public interface ExtIndexFormat extends IndexFormat { + + /** returns label at column index. Labels are for human use only */ + CharSequence label(int index); + /** set label at column index. Labels are for human use only */ + void label(int index, CharSequence label); + + /** returns value type expected for a given column index. For human and computer use */ + ValueType type(int index); + /** sets the type expected for a given column index. For human and computer use */ + void type(int index, ValueType type); + + /** create a new bundle based on this format */ + ExtIndexBundle newBundle(); + + /** alias for getFieldCount */ + int size(); +} diff --git a/src/main/java/com/addthis/bundle/core/index/ExtendedArrayBundle.java b/src/main/java/com/addthis/bundle/core/index/ExtendedArrayBundle.java new file mode 100644 index 0000000..7c879a2 --- /dev/null +++ b/src/main/java/com/addthis/bundle/core/index/ExtendedArrayBundle.java @@ -0,0 +1,106 @@ +package com.addthis.bundle.core.index; + +import java.util.Iterator; + +import com.addthis.bundle.value.ValueArray; +import com.addthis.bundle.value.ValueCustom; +import com.addthis.bundle.value.ValueFactory; +import com.addthis.bundle.value.ValueMap; +import com.addthis.bundle.value.ValueObject; +import com.addthis.bundle.value.ValueString; + +import com.google.common.collect.Iterators; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +public class ExtendedArrayBundle extends ArrayBundle implements ExtIndexBundle { + + public ExtendedArrayBundle(ExtendedArrayFormat format) { + super(format); + } + + @Override + public ValueString string(int index) { + return value(index).asString(); + } + + @Override + public void string(int index, ValueString string) { + value(index, string); + } + + @Override + public ValueArray array(int index) { + return value(index).asArray(); + } + + @Override + public void array(int index, ValueArray array) { + value(index, array); + } + + @Override + public ValueMap map(int index) { + return value(index).asMap(); + } + + @Override + public void map(int index, ValueMap map) { + value(index, map); + } + + @Override + public ByteBuf buf(int index) { + return Unpooled.wrappedBuffer(value(index).asBytes().getBytes()); + } + + @Override + public void buf(int index, ByteBuf buf) { + if (buf.hasArray()) { + value(index, ValueFactory.create(buf.array())); + } else { + throw new UnsupportedOperationException("only heap bufs supported just now due to ValueBytes legacy"); + } + } + + @Override + public T custom(int index) { + return (T) value(index).asCustom(); + } + + @Override + public void custom(int index, ValueCustom custom) { + value(index, custom); + } + + @Override + public long integer(int index) { + return value(index).asLong().getLong(); + } + + @Override + public void integer(int index, long integer) { + value(index, ValueFactory.create(integer)); + } + + @Override + public double floating(int index) { + return value(index).asDouble().getDouble(); + } + + @Override + public void floating(int index, double floating) { + value(index, ValueFactory.create(floating)); + } + + @Override + public ExtIndexFormat format() { + return (ExtIndexFormat) super.format(); + } + + @Override + public Iterator iterator() { + return Iterators.forArray(valueArray); + } +} diff --git a/src/main/java/com/addthis/bundle/core/index/ExtendedArrayFormat.java b/src/main/java/com/addthis/bundle/core/index/ExtendedArrayFormat.java new file mode 100644 index 0000000..a3a7edc --- /dev/null +++ b/src/main/java/com/addthis/bundle/core/index/ExtendedArrayFormat.java @@ -0,0 +1,127 @@ +/* + * Licensed 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 com.addthis.bundle.core.index; + +import java.util.Arrays; + +import com.addthis.bundle.value.ValueType; + +import com.google.common.base.Objects; + +public class ExtendedArrayFormat implements ExtIndexFormat { + + /** + * for debugging and hinting purposes _only_. Do _NOT_ use like map-bundle field names. + */ + private final CharSequence[] labels; + + /** + * for debugging and hinting about value types. provides more options for implementations + * by reducing their reliance on reified ValueObjects. has applications in serialization + * optimizations and processing pipeline planning. + */ + private final ValueType[] types; + + public ExtendedArrayFormat(int length) { + if (length < 1) { + throw new IllegalArgumentException("length must be greater than zero"); + } + this.types = new ValueType[length]; + this.labels = new CharSequence[length]; + } + + public ExtendedArrayFormat(ValueType[] types, CharSequence[] labels) { + if (types.length != labels.length) { + throw new IllegalArgumentException("types array and labels array must be the same size"); + } + if (types.length < 1) { + throw new IllegalArgumentException("length must be greater than zero"); + } + this.types = types; + this.labels = labels; + } + + public ExtendedArrayFormat(ValueType... types) { + if (types.length < 1) { + throw new IllegalArgumentException("length must be greater than zero"); + } + this.types = types; + this.labels = new CharSequence[types.length]; + } + + public ExtendedArrayFormat(CharSequence... labels) { + if (labels.length < 1) { + throw new IllegalArgumentException("length must be greater than zero"); + } + this.labels = labels; + this.types = new ValueType[labels.length]; + } + + /** + * This constructor shares the labels field of the base object, but + * makes a copy of the type array. It is intended to allow formats + * of the same length and nature to make relatively efficient copy + * on write changes to the type hinting. eg. For before and after + * a transformation on some number of columns. + */ + public ExtendedArrayFormat(ExtendedArrayFormat base) { + this.labels = base.labels; + this.types = new ValueType[labels.length]; + System.arraycopy(base.types, 0, types, 0, types.length); + } + + @Override + public CharSequence label(int index) { + return labels[index]; + } + + @Override + public void label(int index, CharSequence label) { + labels[index] = label; + } + + @Override + public ValueType type(int index) { + return types[index]; + } + + @Override + public void type(int index, ValueType type) { + types[index] = type; + } + + @Override + public ExtIndexBundle newBundle() { + return new ExtendedArrayBundle(this); + } + + @Override + public int size() { + return types.length; + } + + @Override + public String toString() { + return Objects.toStringHelper(this) + .add("length", types.length) + .add("labels", Arrays.toString(labels)) + .add("types", Arrays.toString(types)) + .toString(); + } + + @Override + public int getFieldCount() { + return types.length; + } +} diff --git a/src/main/java/com/addthis/bundle/core/index/IndexBundle.java b/src/main/java/com/addthis/bundle/core/index/IndexBundle.java deleted file mode 100644 index 96afe82..0000000 --- a/src/main/java/com/addthis/bundle/core/index/IndexBundle.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Licensed 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 com.addthis.bundle.core.index; - -import java.util.Arrays; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import com.addthis.bundle.core.Bundle; -import com.addthis.bundle.core.BundleException; -import com.addthis.bundle.core.BundleField; -import com.addthis.bundle.core.BundleFormat; -import com.addthis.bundle.value.ValueFactory; -import com.addthis.bundle.value.ValueObject; - -public class IndexBundle implements Bundle { - - //marker object for fields explicitly set to null to allow lazy consistency with the format - private static final ValueObject NULL = ValueFactory.create("__user_null__"); - - private final ValueObject[] bundle; - private final IndexBundleFormat format; - - private int count; - - public IndexBundle(IndexBundleFormat format) { - this.format = format; - this.bundle = new ValueObject[format.length]; - } - - @Override - public String toString() { - return Arrays.toString(bundle); - } - - @Override - public Iterator iterator() { - return new Iterator() { - int index = 0; - private BundleField peek = null; - - @Override - public boolean hasNext() { - while ((peek == null) && (index < bundle.length)) { - ValueObject value = bundle[index]; - index += 1; - if (value == null) { // unset field, user nulls are NULL and those are returned - continue; - } - peek = new IndexBundleField(index); - break; - } - return peek != null; - } - - @Override - public BundleField next() { - if (hasNext()) { - BundleField next = peek; - peek = null; - return next; - } - throw new NoSuchElementException(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException("Field iterator is read-only"); - } - - }; - } - - @Override - public ValueObject getValue(BundleField field) throws BundleException { - ValueObject value = bundle[field.getIndex()]; - if (value == NULL) { - return null; - } - return value; - } - - @Override - public void setValue(BundleField field, ValueObject value) throws BundleException { - if (value == null) { - value = NULL; - } - rawSet(field.getIndex(), value); - } - - // convenience methods for Index Bundles - public void setValue(int index, ValueObject value) throws BundleException { - if (value == null) { - rawSet(index, NULL); - } else { - rawSet(index, value); - } - } - - public void setValue(int index, String value) throws BundleException { - if (value == null) { - rawSet(index, NULL); - } else { - rawSet(index, ValueFactory.create(value)); - } - } - - private void rawSet(int index, ValueObject value) throws BundleException { - ValueObject prev = bundle[index]; - bundle[index] = value; - if (prev == null) { - count++; - } - } - - @Override - public void removeValue(BundleField field) throws BundleException { - rawSet(field.getIndex(), null); - count--; - } - - @Override - public BundleFormat getFormat() { - return format; - } - - @Override - public int getCount() { - return count; - } - - @Override - public Bundle createBundle() { - return new IndexBundle(format); - } -} diff --git a/src/main/java/com/addthis/bundle/core/index/IndexBundleField.java b/src/main/java/com/addthis/bundle/core/index/IndexBundleField.java deleted file mode 100644 index e66532d..0000000 --- a/src/main/java/com/addthis/bundle/core/index/IndexBundleField.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Licensed 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 com.addthis.bundle.core.index; - -import com.addthis.bundle.core.BundleField; - -class IndexBundleField implements BundleField { - - private final int pos; - - IndexBundleField(int pos) { - this.pos = pos; - } - - @Override - public String toString() { - return String.valueOf(pos); - } - - @Override - public Integer getIndex() { - return pos; - } - - @Override - public String getName() { - throw new UnsupportedOperationException("Index bundles don't have fields indexed by strings"); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - - if (!(obj instanceof IndexBundleField)) { - return false; - } - - IndexBundleField that = (IndexBundleField) obj; - - return pos == that.pos; - } - - @Override - public int hashCode() { - return pos; - } -} diff --git a/src/main/java/com/addthis/bundle/core/index/IndexBundleFormat.java b/src/main/java/com/addthis/bundle/core/index/IndexBundleFormat.java deleted file mode 100644 index eee387b..0000000 --- a/src/main/java/com/addthis/bundle/core/index/IndexBundleFormat.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Licensed 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 com.addthis.bundle.core.index; - -import java.util.Iterator; -import java.util.NoSuchElementException; -import java.util.concurrent.CopyOnWriteArrayList; - -import com.addthis.bundle.core.BundleField; -import com.addthis.bundle.core.BundleFormat; - -import com.google.common.base.Objects; - -/** - * This class could do some String to Integer and vice versa auto conversion for some of its methods. - * It does not do so intentionally. Its purpose is to root out such tricks that may cause subtle bugs - * in downstream code. - */ -public class IndexBundleFormat implements BundleFormat { - - final int length; - - /** - * for debugging and hinting purposes _only_. Do _NOT_ use - * like normal field names. - */ - private final CopyOnWriteArrayList labels; - - public IndexBundleFormat(int length) { - this.length = length; - this.labels = null; - } - - public IndexBundleFormat(String... labels) { - this.length = labels.length; - this.labels = new CopyOnWriteArrayList<>(labels); - } - - public String getLabel(int index) { - return labels.get(index); - } - - public String setLabel(int index, String label) { - return labels.set(index, label); - } - - @Override - public String toString() { - return Objects.toStringHelper(this) - .add("length", length) - .add("labels", labels) - .toString(); - } - - @Override - public Object getVersion() { - return this; - } - - @Override - public Iterator iterator() { - return new Iterator() { - int index = 0; - - @Override - public boolean hasNext() { - return index < length; - } - - @Override - public BundleField next() { - if (hasNext()) { - BundleField next = new IndexBundleField(index); - index += 1; - return next; - } - throw new NoSuchElementException(); - } - - @Override - public void remove() { - throw new UnsupportedOperationException("Field iterator is read-only"); - } - - }; - } - - @Override - public boolean hasField(String name) { - throw new UnsupportedOperationException("Index Bundle Format does not index fields by Strings"); - } - - @Override - public BundleField getField(String name) { - throw new UnsupportedOperationException("Index Bundle Format does not index fields by Strings"); - } - - @Override - public BundleField getField(int pos) { - if (pos < length) { - return new IndexBundleField(pos); - } else { - throw new IndexOutOfBoundsException("Index formats use an immutable size to encourage caution"); - } - } - - @Override - public int getFieldCount() { - return length; - } -} diff --git a/src/main/java/com/addthis/bundle/core/kvp/KVBundle.java b/src/main/java/com/addthis/bundle/core/kvp/KVBundle.java index bf20b5a..92c581f 100644 --- a/src/main/java/com/addthis/bundle/core/kvp/KVBundle.java +++ b/src/main/java/com/addthis/bundle/core/kvp/KVBundle.java @@ -88,4 +88,19 @@ public int getCount() { public Bundle createBundle() { return new KVBundle(format); } + + @Override + public ValueObject value(int index) { + throw new UnsupportedOperationException("KV Bundles are map-only"); + } + + @Override + public void value(int index, ValueObject value) { + throw new UnsupportedOperationException("KV Bundles are map-only"); + } + + @Override + public BundleFormat format() { + return getFormat(); + } } diff --git a/src/main/java/com/addthis/bundle/core/list/ListBundle.java b/src/main/java/com/addthis/bundle/core/list/ListBundle.java index d63d358..19a8576 100644 --- a/src/main/java/com/addthis/bundle/core/list/ListBundle.java +++ b/src/main/java/com/addthis/bundle/core/list/ListBundle.java @@ -103,16 +103,20 @@ public ValueObject getValue(BundleField field) throws BundleException { return value; } + private ValueObject getRawValueByIndex(int index) throws BundleException { + if (index < bundle.size()) { + return bundle.get(index); + } else { + return SKIP; + } + } + private ValueObject getRawValue(BundleField field) throws BundleException { - if (field != null) { - Integer index = field.getIndex(); - if (index < bundle.size()) { - return bundle.get(index); - } else { - return SKIP; - } + if (field == null) { + return null; } - return null; + int index = field.getIndex(); + return getRawValueByIndex(index); } @Override @@ -159,4 +163,23 @@ public int getCount() { public Bundle createBundle() { return new ListBundle(format); } + + @Override + public ValueObject value(int index) { + ValueObject value = getRawValueByIndex(index); + if (value == SKIP) { + return null; + } + return value; + } + + @Override + public void value(int index, ValueObject value) { + + } + + @Override + public BundleFormat format() { + return getFormat(); + } } diff --git a/src/main/java/com/addthis/bundle/io/DataChannelCodec.java b/src/main/java/com/addthis/bundle/io/DataChannelCodec.java index 4e246e0..c912bba 100644 --- a/src/main/java/com/addthis/bundle/io/DataChannelCodec.java +++ b/src/main/java/com/addthis/bundle/io/DataChannelCodec.java @@ -26,6 +26,7 @@ import com.addthis.bundle.core.Bundle; import com.addthis.bundle.core.BundleField; import com.addthis.bundle.table.DataTable; +import com.addthis.bundle.value.ValueType; import com.addthis.bundle.value.ValueArray; import com.addthis.bundle.value.ValueCustom; import com.addthis.bundle.value.ValueFactory; @@ -176,7 +177,7 @@ public static void encodeValue(ValueObject val, OutputStream out, ClassIndexMap out.write(TYPE.NULL.val); return; } - ValueObject.TYPE objectType = val.getObjectType(); + ValueType objectType = val.getObjectType(); switch (objectType) { case CUSTOM: ValueCustom custom = val.asCustom(); diff --git a/src/main/java/com/addthis/bundle/io/index/ByteBufs.java b/src/main/java/com/addthis/bundle/io/index/ByteBufs.java new file mode 100644 index 0000000..f205bbf --- /dev/null +++ b/src/main/java/com/addthis/bundle/io/index/ByteBufs.java @@ -0,0 +1,148 @@ +/* + * Licensed 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 com.addthis.bundle.io.index; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; + +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.util.CharsetUtil; + +public class ByteBufs { + + public static final ByteBufAllocator ALLOC = PooledByteBufAllocator.DEFAULT; + + public static ByteBuf fromString(String string) { + return ByteBufUtil.encodeString(ALLOC, CharBuffer.wrap(string), CharsetUtil.UTF_8); + } + + public static String toString(ByteBuf buf) { + return buf.toString(CharsetUtil.UTF_8); + } + + public static ByteBuf quickAlloc() { + return ALLOC.buffer(); + } + + public static ByteBufOutputStream allocOutputStream() { + return new ByteBufOutputStream(ALLOC.buffer()); + } + + public static void writeString(String string, ByteBuf to) { +// ByteBuf stringBuf = ByteBufs.fromString(string); + byte[] bytes = string.getBytes(StandardCharsets.UTF_8); + writeLength((long) bytes.length, to); + to.writeBytes(bytes); +// stringBuf.release(); + } + + public static String readString(ByteBuf from) throws IOException { + long length = readLength(from); + ByteBuf stringSlice = from.readSlice((int) length); + return toString(stringSlice); + } + + public static byte[] readBytes(ByteBuf from) throws IOException { + long length = readLength(from); + ByteBuf slice = from.readSlice((int) length); + byte[] bytes = new byte[slice.readableBytes()]; + slice.readBytes(bytes); + return bytes; + } + + public static void writeBytes(byte[] bytes, ByteBuf to) throws IOException { + long length = bytes.length; + writeLength(length, to); + to.writeBytes(bytes); + } + + public static byte[] toBytes(ByteBuf buf) { + int length = buf.readableBytes(); + byte[] rawBytes = new byte[length]; + buf.readBytes(rawBytes); + return rawBytes; + } + + /** + * Read an InputStream to it's end and return as a ByteBuf + * + * @param in + * @return + * @throws java.io.IOException + */ + public static ByteBuf readFully(InputStream in) throws IOException { + ByteBuf buf = ByteBufs.quickAlloc(); + while (buf.writeBytes(in, 1024) >= 0) { + // read more + } + return buf; + } + + /** + * Write a length field to a ByteBuf. + * + * @param size + * @param buf + * @throws java.io.IOException + */ + public static void writeLength(long size, ByteBuf buf) { + if (size < 0) { + throw new IllegalArgumentException("writeLength value must be >= 0: " + size); + } + if (size == 0) { + buf.writeByte(0); + return; + } + while (size > 0) { + if (size > 0x7f) { + buf.writeByte((int) (0x80 | (size & 0x7f))); + } else { + buf.writeByte((int) (size & 0x7f)); + } + size >>= 7; + } + } + + /** + * Read a length field from a ByteBuf + * + * @param in + * @return + * @throws java.io.IOException + */ + public static long readLength(ByteBuf in) throws IOException { + long size = 0; + long iter = 0; + long next = 0; + do { + if (!in.isReadable()) { + throw new EOFException(); + } + next = in.readByte(); + size |= ((next & 0x7f) << iter); + iter += 7; + } + while ((next & 0x80) == 0x80); + return size; + } +} diff --git a/src/main/java/com/addthis/bundle/io/index/IndexBundleCodec.java b/src/main/java/com/addthis/bundle/io/index/IndexBundleCodec.java new file mode 100644 index 0000000..cdd9b0b --- /dev/null +++ b/src/main/java/com/addthis/bundle/io/index/IndexBundleCodec.java @@ -0,0 +1,207 @@ +/* + * Licensed 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 com.addthis.bundle.io.index; + +import java.io.IOException; + +import java.util.ArrayList; +import java.util.List; + +import com.addthis.bundle.core.index.ExtIndexBundle; +import com.addthis.bundle.value.ValueArray; +import com.addthis.bundle.value.ValueCustom; +import com.addthis.bundle.value.ValueFactory; +import com.addthis.bundle.value.ValueMap; +import com.addthis.bundle.value.ValueMapEntry; +import com.addthis.bundle.value.ValueObject; +import com.addthis.bundle.value.ValueType; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.netty.buffer.ByteBuf; + +/** + * clean version of DataChannelCodec that uses ByteBufs and nameless fields (index bundles) + * + * bundles must be encoded and decoded with the same number of columns + */ +public final class IndexBundleCodec { + + private static final Logger log = LoggerFactory.getLogger(IndexBundleCodec.class); + + private static enum TYPE { + NULL, STRING, BYTES, LONG, LONG_NEG, LONG_BIG, + DOUBLE, ARRAY, MAP, CUSTOM_INDEX, CUSTOM_CLASS + } + + private static final TYPE[] TYPES = TYPE.values(); + + private final List> classList = new ArrayList<>(0); + + public void encodeBundle(ExtIndexBundle row, ByteBuf out) throws IOException { + for (ValueObject value : row) { + encodeValue(value, out); + } + } + + public void encodeValue(ValueObject val, ByteBuf out) throws IOException { + if (val == null) { + out.writeByte(TYPE.NULL.ordinal()); + return; + } + ValueType objectType = val.getObjectType(); + switch (objectType) { + case CUSTOM: + ValueCustom custom = val.asCustom(); + Class type = custom.getContainerClass(); + int classIndex = classList.indexOf(type); + if (classIndex == -1) { + classIndex = classList.size(); + classList.add(type); + out.writeByte(TYPE.CUSTOM_CLASS.ordinal()); + ByteBufs.writeLength(classIndex, out); + ByteBufs.writeString(type.getName(), out); + } else { + out.writeByte(TYPE.CUSTOM_INDEX.ordinal()); + ByteBufs.writeLength(classIndex, out); + } + encodeValue(custom.asMap(), out); + break; + case MAP: + ValueMap map = val.asMap(); + out.writeByte(TYPE.MAP.ordinal()); + ByteBufs.writeLength(map.size(), out); + for (ValueMapEntry e : map) { + encodeValue(ValueFactory.create(e.getKey()), out); + encodeValue(e.getValue(), out); + } + break; + case ARRAY: + out.writeByte(TYPE.ARRAY.ordinal()); + ValueArray arr = val.asArray(); + ByteBufs.writeLength(arr.size(), out); + for (ValueObject vo : arr) { + encodeValue(vo, out); + } + break; + case STRING: + out.writeByte(TYPE.STRING.ordinal()); + ByteBufs.writeString(val.asString().getString(), out); + break; + case BYTES: + out.writeByte(TYPE.BYTES.ordinal()); + ByteBufs.writeBytes(val.asBytes().getBytes(), out); + break; + case INT: + long lv = val.asLong().getLong(); + // over 2^48, direct bytes are more efficient + if (lv > 281474976710656L) { + out.writeByte(TYPE.LONG_BIG.ordinal()); + out.writeLong(lv); + break; + } + if (lv >= 0) { + out.writeByte(TYPE.LONG.ordinal()); + } else { + out.writeByte(TYPE.LONG_NEG.ordinal()); + lv = -lv; + } + ByteBufs.writeLength(lv, out); + break; + case FLOAT: + long dv = Double.doubleToLongBits(val.asDouble().getDouble()); + out.writeByte(TYPE.DOUBLE.ordinal()); + out.writeLong(dv); + break; + default: + log.error("Unknown object type {}", objectType); + throw new IOException("Unknown object type " + objectType); + } + } + + public ExtIndexBundle decodeBundle(ExtIndexBundle bundle, ByteBuf in) throws IOException { + for (int i = 0; i < bundle.format().size(); i++) { + ValueObject nextValue = decodeValue(in); + bundle.value(i, nextValue); + } + return bundle; + } + + public ValueObject decodeValue(ByteBuf in) throws IOException { + byte typeInteger = in.readByte(); + TYPE type = TYPES[(int) typeInteger]; + switch (type) { + case NULL: + return null; + case BYTES: + return ValueFactory.create(ByteBufs.readBytes(in)); + case LONG: + return ValueFactory.create(ByteBufs.readLength(in)); + case LONG_NEG: + return ValueFactory.create(-ByteBufs.readLength(in)); + case LONG_BIG: + return ValueFactory.create(in.readLong()); + case DOUBLE: + return ValueFactory.create(Double.longBitsToDouble(in.readLong())); + case STRING: + return ValueFactory.create(ByteBufs.readString(in)); + case ARRAY: + int len = (int) ByteBufs.readLength(in); + ValueArray arr = ValueFactory.createArray(len); + for (int i = 0; i < len; i++) { + arr.add(decodeValue(in)); + } + return arr; + case MAP: + ValueMap map = ValueFactory.createMap(); + long count = ByteBufs.readLength(in); + while (count-- > 0) { + ValueObject key = decodeValue(in); + ValueObject val = decodeValue(in); + map.put(key.toString(), val); + } + return map; + case CUSTOM_INDEX: + Class ci = classList.get((int) ByteBufs.readLength(in)); + return rehydrate(ci, in); + case CUSTOM_CLASS: + int index = (int) ByteBufs.readLength(in); + Class cc; + try { + cc = (Class) Class.forName(ByteBufs.readString(in)); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + if (classList.size() != index) { + throw new RuntimeException("index conflict for " + cc + " @ " + index); + } + classList.add(cc); + return rehydrate(cc, in); + default: + throw new RuntimeException("invalid decode type " + type); + } + } + + private ValueCustom rehydrate(Class cc, ByteBuf in) { + try { + ValueCustom vc = cc.newInstance().asCustom(); + ValueMap map = decodeValue(in).asMap(); + vc.setValues(map); + return vc; + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/main/java/com/addthis/bundle/util/ValueUtil.java b/src/main/java/com/addthis/bundle/util/ValueUtil.java index 4bba158..7bc7708 100644 --- a/src/main/java/com/addthis/bundle/util/ValueUtil.java +++ b/src/main/java/com/addthis/bundle/util/ValueUtil.java @@ -13,6 +13,7 @@ */ package com.addthis.bundle.util; +import com.addthis.bundle.value.ValueType; import com.addthis.bundle.value.ValueArray; import com.addthis.bundle.value.ValueFactory; import com.addthis.bundle.value.ValueNumber; @@ -36,21 +37,21 @@ public static boolean isEqual(ValueObject v1, ValueObject v2) { * return true if null value or value is equivalent to an empty string */ public static boolean isEmpty(ValueObject v) { - return v == null || (v.getObjectType() == ValueObject.TYPE.STRING && v.toString().length() == 0); + return v == null || (v.getObjectType() == ValueType.STRING && v.toString().length() == 0); } /** * @return String or null if it is null or not convertible */ public static String asNativeString(ValueObject v) { - return v != null ? v.getObjectType() == ValueObject.TYPE.STRING ? v.asString().getString() : v.toString() : null; + return v != null ? v.getObjectType() == ValueType.STRING ? v.asString().getString() : v.toString() : null; } /** * @return String or null if it is null or not convertible */ public static ValueString asString(ValueObject v) { - return v != null ? v.getObjectType() == ValueObject.TYPE.STRING ? v.asString() : ValueFactory.create(v.toString()) : null; + return v != null ? v.getObjectType() == ValueType.STRING ? v.asString() : ValueFactory.create(v.toString()) : null; } /** @@ -60,7 +61,7 @@ public static ValueArray asArray(ValueObject v) { if (v == null) { return null; } - if (v.getObjectType() == ValueObject.TYPE.ARRAY) { + if (v.getObjectType() == ValueType.ARRAY) { return v.asArray(); } ValueArray arr = ValueFactory.createArray(1); @@ -73,11 +74,11 @@ public static ValueArray asArray(ValueObject v) { */ public static ValueNumber asNumber(ValueObject v) { if (v != null) { - ValueObject.TYPE t = v.getObjectType(); - if (t == ValueObject.TYPE.INT) { + ValueType t = v.getObjectType(); + if (t == ValueType.INT) { return v.asLong(); } - if (t == ValueObject.TYPE.FLOAT) { + if (t == ValueType.FLOAT) { return v.asDouble(); } if (v instanceof ValueNumber) { diff --git a/src/main/java/com/addthis/bundle/value/DefaultArray.java b/src/main/java/com/addthis/bundle/value/DefaultArray.java index ff94b92..0725ee6 100644 --- a/src/main/java/com/addthis/bundle/value/DefaultArray.java +++ b/src/main/java/com/addthis/bundle/value/DefaultArray.java @@ -23,8 +23,8 @@ protected DefaultArray(int size) { } @Override - public TYPE getObjectType() { - return TYPE.ARRAY; + public ValueType getObjectType() { + return ValueType.ARRAY; } @Override diff --git a/src/main/java/com/addthis/bundle/value/DefaultBytes.java b/src/main/java/com/addthis/bundle/value/DefaultBytes.java index 674f6b3..f34344a 100644 --- a/src/main/java/com/addthis/bundle/value/DefaultBytes.java +++ b/src/main/java/com/addthis/bundle/value/DefaultBytes.java @@ -29,8 +29,8 @@ public String toString() { } @Override - public TYPE getObjectType() { - return TYPE.BYTES; + public ValueType getObjectType() { + return ValueType.BYTES; } @Override diff --git a/src/main/java/com/addthis/bundle/value/DefaultDouble.java b/src/main/java/com/addthis/bundle/value/DefaultDouble.java index 624d107..a0e4b2f 100644 --- a/src/main/java/com/addthis/bundle/value/DefaultDouble.java +++ b/src/main/java/com/addthis/bundle/value/DefaultDouble.java @@ -69,8 +69,8 @@ public String toString() { } @Override - public TYPE getObjectType() { - return TYPE.FLOAT; + public ValueType getObjectType() { + return ValueType.FLOAT; } @Override diff --git a/src/main/java/com/addthis/bundle/value/DefaultLong.java b/src/main/java/com/addthis/bundle/value/DefaultLong.java index 0382b3f..c1e126c 100644 --- a/src/main/java/com/addthis/bundle/value/DefaultLong.java +++ b/src/main/java/com/addthis/bundle/value/DefaultLong.java @@ -69,8 +69,8 @@ public String toString() { } @Override - public TYPE getObjectType() { - return TYPE.INT; + public ValueType getObjectType() { + return ValueType.INT; } @Override diff --git a/src/main/java/com/addthis/bundle/value/DefaultMap.java b/src/main/java/com/addthis/bundle/value/DefaultMap.java index fe799a4..284c729 100644 --- a/src/main/java/com/addthis/bundle/value/DefaultMap.java +++ b/src/main/java/com/addthis/bundle/value/DefaultMap.java @@ -23,8 +23,8 @@ protected DefaultMap() { } @Override - public TYPE getObjectType() { - return TYPE.MAP; + public ValueType getObjectType() { + return ValueType.MAP; } @Override diff --git a/src/main/java/com/addthis/bundle/value/DefaultString.java b/src/main/java/com/addthis/bundle/value/DefaultString.java index c803515..91deb63 100644 --- a/src/main/java/com/addthis/bundle/value/DefaultString.java +++ b/src/main/java/com/addthis/bundle/value/DefaultString.java @@ -36,8 +36,8 @@ public boolean equals(Object o) { } @Override - public TYPE getObjectType() { - return TYPE.STRING; + public ValueType getObjectType() { + return ValueType.STRING; } @Override diff --git a/src/main/java/com/addthis/bundle/value/ValueFactory.java b/src/main/java/com/addthis/bundle/value/ValueFactory.java index ab8c4a3..3268117 100644 --- a/src/main/java/com/addthis/bundle/value/ValueFactory.java +++ b/src/main/java/com/addthis/bundle/value/ValueFactory.java @@ -22,7 +22,7 @@ public static ValueString create(String val) { return val != null ? new DefaultString(val) : null; } - public static ValueBytes create(byte val[]) { + public static ValueBytes create(byte[] val) { return val != null ? new DefaultBytes(val) : null; } @@ -75,7 +75,7 @@ public static ValueCustom createCustom(Class clazz, Value public static ValueObject copyValue(ValueObject valueObject) { ValueObject newValueObject = null; if (valueObject != null) { - ValueObject.TYPE type = valueObject.getObjectType(); + ValueType type = valueObject.getObjectType(); switch (type) { case STRING: newValueObject = ValueFactory.create(valueObject.asString().getString()); diff --git a/src/main/java/com/addthis/bundle/value/ValueObject.java b/src/main/java/com/addthis/bundle/value/ValueObject.java index 05baf63..92261de 100644 --- a/src/main/java/com/addthis/bundle/value/ValueObject.java +++ b/src/main/java/com/addthis/bundle/value/ValueObject.java @@ -19,11 +19,7 @@ */ public interface ValueObject { - public static enum TYPE { - STRING, INT, FLOAT, BYTES, ARRAY, MAP, CUSTOM - }; - - public TYPE getObjectType(); + public ValueType getObjectType(); public ValueBytes asBytes() throws ValueTranslationException; diff --git a/src/main/java/com/addthis/bundle/value/ValueType.java b/src/main/java/com/addthis/bundle/value/ValueType.java new file mode 100644 index 0000000..104019a --- /dev/null +++ b/src/main/java/com/addthis/bundle/value/ValueType.java @@ -0,0 +1,5 @@ +package com.addthis.bundle.value; + +public enum ValueType { + STRING, INT, FLOAT, BYTES, ARRAY, MAP, CUSTOM +} diff --git a/src/test/java/com/addthis/bundle/core/index/IndexBundleFormatTest.java b/src/test/java/com/addthis/bundle/core/index/IndexBundleFormatTest.java deleted file mode 100644 index 1c69bc1..0000000 --- a/src/test/java/com/addthis/bundle/core/index/IndexBundleFormatTest.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.addthis.bundle.core.index; - -import com.google.common.collect.Iterators; - -import org.junit.Assert; -import org.junit.Test; - -public class IndexBundleFormatTest { - - @Test - public void iterateCorrectly() { - IndexBundleFormat format = new IndexBundleFormat(5); - Assert.assertEquals("Index Format should iterate over each column", - 5, Iterators.size(format.iterator())); - - IndexBundleFormat formatStrings = new IndexBundleFormat("one", "two", "merge"); - Assert.assertEquals("Index Format should iterate over each column", - 3, Iterators.size(formatStrings.iterator())); - } - -} diff --git a/src/test/java/com/addthis/bundle/core/index/IndexBundleTest.java b/src/test/java/com/addthis/bundle/core/index/IndexBundleTest.java deleted file mode 100644 index 74846fd..0000000 --- a/src/test/java/com/addthis/bundle/core/index/IndexBundleTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.addthis.bundle.core.index; - -import java.util.Iterator; - -import com.addthis.bundle.core.BundleField; - -import com.google.common.collect.Iterators; - -import org.junit.Assert; -import org.junit.Test; - -public class IndexBundleTest { - - @Test - public void iterateCorrectly() { - IndexBundleFormat format = new IndexBundleFormat(5); - IndexBundle bundle = new IndexBundle(format); - - Assert.assertEquals("Index Bundles should iterate over only columns set for that bundle", - 0, Iterators.size(bundle.iterator())); - - bundle.setValue(0, "hey"); - bundle.setValue(1, "thre"); - bundle.setValue(1, "there"); - - Assert.assertEquals("Index Bundle should iterate over only columns set for that bundle", - 2, Iterators.size(bundle.iterator())); - - bundle.removeValue(new IndexBundleField(0)); - - Assert.assertEquals("Index Bundle should iterate over only columns set for that bundle", - 1, Iterators.size(bundle.iterator())); - - Iterator iterator = bundle.iterator(); - - Assert.assertTrue("Iterator hasNext() should not advance iterator", iterator.hasNext()); - Assert.assertTrue("Iterator hasNext() should not advance iterator", iterator.hasNext()); - Assert.assertNotNull(iterator.next()); - Assert.assertFalse("Iterator hasNext() should not advance iterator", iterator.hasNext()); - Assert.assertFalse("Iterator hasNext() should not advance iterator", iterator.hasNext()); - - } - - @Test - public void countFields() { - IndexBundleFormat format = new IndexBundleFormat(5); - IndexBundle bundle = new IndexBundle(format); - - bundle.setValue(0, "hey"); - bundle.setValue(1, "thre"); - bundle.setValue(1, "there"); - - Assert.assertEquals("Index Bundle count should be the number of set fields", - 2, bundle.getCount()); - - bundle.removeValue(new IndexBundleField(0)); - - Assert.assertEquals("Index Bundle count should be the number of set fields", - 1, bundle.getCount()); - - bundle.setValue(0, "hiya"); - - Assert.assertEquals("Index Bundle count should be the number of set fields", - 2, bundle.getCount()); - - bundle.setValue(3, "friend"); - - Assert.assertEquals("Index Bundle count should be the number of set fields", - 3, bundle.getCount()); - } - -} diff --git a/src/test/java/com/addthis/bundle/io/IndexBundleCodecTest.java b/src/test/java/com/addthis/bundle/io/IndexBundleCodecTest.java new file mode 100644 index 0000000..aa1d2e7 --- /dev/null +++ b/src/test/java/com/addthis/bundle/io/IndexBundleCodecTest.java @@ -0,0 +1,52 @@ +/* + * Licensed 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 com.addthis.bundle.io; + +import com.addthis.bundle.core.index.ExtIndexBundle; +import com.addthis.bundle.core.index.ExtIndexFormat; +import com.addthis.bundle.core.index.ExtendedArrayFormat; +import com.addthis.bundle.io.index.ByteBufs; +import com.addthis.bundle.io.index.IndexBundleCodec; +import com.addthis.bundle.value.ValueFactory; + +import org.junit.Test; + +import io.netty.buffer.ByteBuf; +import static org.junit.Assert.assertEquals; + +public class IndexBundleCodecTest { + + public static void paranoidAssertBundlesEqual(ExtIndexBundle b1, ExtIndexBundle b2) throws Exception { + assertEquals(b1.toString(), b2.toString()); + ByteBuf buf1 = ByteBufs.quickAlloc(); + ByteBuf buf2 = ByteBufs.quickAlloc(); + new IndexBundleCodec().encodeBundle(b1, buf1); + new IndexBundleCodec().encodeBundle(b2, buf2); + assertEquals(buf1, buf2); + } + + @Test + public void roundTrip() throws Exception { + ExtIndexFormat format = new ExtendedArrayFormat(3); + ExtIndexBundle bundle1 = format.newBundle(); + bundle1.string(0, ValueFactory.create("heya")); + bundle1.string(1, ValueFactory.create("there")); + bundle1.integer(2, 42); + ExtIndexBundle bundle2 = format.newBundle(); + ByteBuf buf1 = ByteBufs.quickAlloc(); + new IndexBundleCodec().encodeBundle(bundle1, buf1); + new IndexBundleCodec().decodeBundle(bundle2, buf1); + paranoidAssertBundlesEqual(bundle1, bundle2); + } +}