Details
diff --git a/usage/src/main/java/com/ning/billing/usage/timeline/samples/NullSample.java b/usage/src/main/java/com/ning/billing/usage/timeline/samples/NullSample.java
new file mode 100644
index 0000000..efa99fa
--- /dev/null
+++ b/usage/src/main/java/com/ning/billing/usage/timeline/samples/NullSample.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.usage.timeline.samples;
+
+public class NullSample extends ScalarSample<Void> {
+
+ public NullSample() {
+ super(SampleOpcode.NULL, null);
+ }
+}
diff --git a/usage/src/main/java/com/ning/billing/usage/timeline/samples/RepeatSample.java b/usage/src/main/java/com/ning/billing/usage/timeline/samples/RepeatSample.java
new file mode 100644
index 0000000..990ef64
--- /dev/null
+++ b/usage/src/main/java/com/ning/billing/usage/timeline/samples/RepeatSample.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.usage.timeline.samples;
+
+/**
+ * A repeated value
+ *
+ * @param <T> A value consistent with the opcode
+ */
+public class RepeatSample<T> extends SampleBase {
+
+ public static final int MAX_BYTE_REPEAT_COUNT = 0xFF; // The maximum byte value
+ public static final int MAX_SHORT_REPEAT_COUNT = 0xFFFF; // The maximum short value
+
+ private final ScalarSample<T> sampleRepeated;
+
+ private int repeatCount;
+
+ public RepeatSample(final int repeatCount, final ScalarSample<T> sampleRepeated) {
+ super(SampleOpcode.REPEAT_BYTE);
+ this.repeatCount = repeatCount;
+ this.sampleRepeated = sampleRepeated;
+ }
+
+ public int getRepeatCount() {
+ return repeatCount;
+ }
+
+ public void incrementRepeatCount() {
+ repeatCount++;
+ }
+
+ public void incrementRepeatCount(final int addend) {
+ repeatCount += addend;
+ }
+
+ public ScalarSample<T> getSampleRepeated() {
+ return sampleRepeated;
+ }
+
+ @Override
+ public SampleOpcode getOpcode() {
+ return repeatCount > MAX_BYTE_REPEAT_COUNT ? SampleOpcode.REPEAT_SHORT : SampleOpcode.REPEAT_BYTE;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ sb.append("RepeatSample");
+ sb.append("{sampleRepeated=").append(sampleRepeated);
+ sb.append(", repeatCount=").append(repeatCount);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(final Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ final RepeatSample that = (RepeatSample) o;
+
+ if (repeatCount != that.repeatCount) {
+ return false;
+ }
+ if (sampleRepeated != null ? !sampleRepeated.equals(that.sampleRepeated) : that.sampleRepeated != null) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = sampleRepeated != null ? sampleRepeated.hashCode() : 0;
+ result = 31 * result + repeatCount;
+ return result;
+ }
+}
diff --git a/usage/src/main/java/com/ning/billing/usage/timeline/samples/SampleBase.java b/usage/src/main/java/com/ning/billing/usage/timeline/samples/SampleBase.java
new file mode 100644
index 0000000..25072e5
--- /dev/null
+++ b/usage/src/main/java/com/ning/billing/usage/timeline/samples/SampleBase.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.usage.timeline.samples;
+
+public abstract class SampleBase {
+
+ protected final SampleOpcode opcode;
+
+ public SampleBase(final SampleOpcode opcode) {
+ this.opcode = opcode;
+ }
+
+ public SampleOpcode getOpcode() {
+ return opcode;
+ }
+}
diff --git a/usage/src/main/java/com/ning/billing/usage/timeline/samples/ScalarSample.java b/usage/src/main/java/com/ning/billing/usage/timeline/samples/ScalarSample.java
new file mode 100644
index 0000000..a4f3173
--- /dev/null
+++ b/usage/src/main/java/com/ning/billing/usage/timeline/samples/ScalarSample.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2010-2012 Ning, Inc.
+ *
+ * Ning 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 com.ning.billing.usage.timeline.samples;
+
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigInteger;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonValue;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.primitives.Ints;
+import com.google.common.primitives.Shorts;
+
+/**
+ * A sample value associated with its opcode
+ *
+ * @param <T> A value consistent with the opcode
+ */
+public class ScalarSample<T> extends SampleBase {
+
+ private static final String KEY_OPCODE = "O";
+ private static final String KEY_SAMPLE_CLASS = "K";
+ private static final String KEY_SAMPLE_VALUE = "V";
+
+ private final T sampleValue;
+
+ public static ScalarSample fromObject(final Object sampleValue) {
+ if (sampleValue == null) {
+ return new ScalarSample<Void>(SampleOpcode.NULL, null);
+ } else if (sampleValue instanceof Byte) {
+ return new ScalarSample<Byte>(SampleOpcode.BYTE, (Byte) sampleValue);
+ } else if (sampleValue instanceof Short) {
+ return new ScalarSample<Short>(SampleOpcode.SHORT, (Short) sampleValue);
+ } else if (sampleValue instanceof Integer) {
+ try {
+ // Can it fit in a short?
+ final short optimizedShort = Shorts.checkedCast(Long.valueOf(sampleValue.toString()));
+ return new ScalarSample<Short>(SampleOpcode.SHORT, optimizedShort);
+ } catch (IllegalArgumentException e) {
+ return new ScalarSample<Integer>(SampleOpcode.INT, (Integer) sampleValue);
+ }
+ } else if (sampleValue instanceof Long) {
+ try {
+ // Can it fit in a short?
+ final short optimizedShort = Shorts.checkedCast(Long.valueOf(sampleValue.toString()));
+ return new ScalarSample<Short>(SampleOpcode.SHORT, optimizedShort);
+ } catch (IllegalArgumentException e) {
+ try {
+ // Can it fit in an int?
+ final int optimizedLong = Ints.checkedCast(Long.valueOf(sampleValue.toString()));
+ return new ScalarSample<Integer>(SampleOpcode.INT, optimizedLong);
+ } catch (IllegalArgumentException ohWell) {
+ return new ScalarSample<Long>(SampleOpcode.LONG, (Long) sampleValue);
+ }
+ }
+ } else if (sampleValue instanceof Float) {
+ return new ScalarSample<Float>(SampleOpcode.FLOAT, (Float) sampleValue);
+ } else if (sampleValue instanceof Double) {
+ return new ScalarSample<Double>(SampleOpcode.DOUBLE, (Double) sampleValue);
+ } else {
+ return new ScalarSample<String>(SampleOpcode.STRING, sampleValue.toString());
+ }
+ }
+
+ public ScalarSample(final SampleOpcode opcode, final T sampleValue) {
+ super(opcode);
+ this.sampleValue = sampleValue;
+ }
+
+ public ScalarSample(final String opcode, final T sampleValue) {
+ this(SampleOpcode.valueOf(opcode), sampleValue);
+ }
+
+ public double getDoubleValue() {
+ final Object sampleValue = getSampleValue();
+ return getDoubleValue(getOpcode(), sampleValue);
+ }
+
+ public static double getDoubleValue(final SampleOpcode opcode, final Object sampleValue) {
+ switch (opcode) {
+ case NULL:
+ case DOUBLE_ZERO:
+ case INT_ZERO:
+ return 0.0;
+ case BYTE:
+ case BYTE_FOR_DOUBLE:
+ return (double) ((Byte) sampleValue);
+ case SHORT:
+ case SHORT_FOR_DOUBLE:
+ return (double) ((Short) sampleValue);
+ case INT:
+ return (double) ((Integer) sampleValue);
+ case LONG:
+ return (double) ((Long) sampleValue);
+ case FLOAT:
+ case FLOAT_FOR_DOUBLE:
+ return (double) ((Float) sampleValue);
+ case HALF_FLOAT_FOR_DOUBLE:
+ return (double) HalfFloat.toFloat((Short) sampleValue);
+ case DOUBLE:
+ return (Double) sampleValue;
+ case BIGINT:
+ return ((BigInteger) sampleValue).doubleValue();
+ default:
+ throw new IllegalArgumentException(String.format("In getDoubleValue(), sample opcode is %s, sample value is %s",
+ opcode.name(), String.valueOf(sampleValue)));
+ }
+ }
+
+ @JsonCreator
+ public ScalarSample(@JsonProperty(KEY_OPCODE) final byte opcodeIdx,
+ @JsonProperty(KEY_SAMPLE_CLASS) final Class klass,
+ @JsonProperty(KEY_SAMPLE_VALUE) final T sampleValue) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
+ super(SampleOpcode.getOpcodeFromIndex(opcodeIdx));
+ // Numerical classes have a String constructor
+ this.sampleValue = (T) klass.getConstructor(String.class).newInstance(sampleValue.toString());
+ }
+
+ @JsonValue
+ public Map<String, Object> toMap() {
+ // Work around type erasure by storing explicitly the sample class. This avoid deserializing shorts as integers
+ // at replay time for instance
+ return ImmutableMap.of(KEY_OPCODE, opcode.getOpcodeIndex(), KEY_SAMPLE_CLASS, sampleValue.getClass(), KEY_SAMPLE_VALUE, sampleValue);
+ }
+
+ public T getSampleValue() {
+ return sampleValue;
+ }
+
+ @Override
+ public String toString() {
+ return sampleValue.toString();
+ }
+
+ @Override
+ public boolean equals(final Object other) {
+ if (other == null || !(other instanceof SampleBase)) {
+ return false;
+ }
+ final ScalarSample otherSample = (ScalarSample) other;
+ final Object otherValue = otherSample.getSampleValue();
+ if (getOpcode() != otherSample.getOpcode()) {
+ return false;
+ } else if (!opcode.getNoArgs() && !(sameSampleValues(sampleValue, otherValue))) {
+ return false;
+ }
+ return true;
+ }
+
+ public static boolean sameSampleValues(final Object o1, final Object o2) {
+ if (o1 == o2) {
+ return true;
+ } else if (o1.getClass() == o2.getClass()) {
+ return o1.equals(o2);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return sampleValue != null ? sampleValue.hashCode() : 0;
+ }
+}
diff --git a/usage/src/test/java/com/ning/billing/usage/timeline/samples/TestNullSample.java b/usage/src/test/java/com/ning/billing/usage/timeline/samples/TestNullSample.java
new file mode 100644
index 0000000..369bba4
--- /dev/null
+++ b/usage/src/test/java/com/ning/billing/usage/timeline/samples/TestNullSample.java
@@ -0,0 +1,15 @@
+package com.ning.billing.usage.timeline.samples;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestNullSample {
+
+ @Test(groups = "fast")
+ public void testConstructor() throws Exception {
+ final NullSample sample = new NullSample();
+
+ Assert.assertEquals(sample.getOpcode(), SampleOpcode.NULL);
+ Assert.assertNull(sample.getSampleValue());
+ }
+}
diff --git a/usage/src/test/java/com/ning/billing/usage/timeline/samples/TestRepeatSample.java b/usage/src/test/java/com/ning/billing/usage/timeline/samples/TestRepeatSample.java
new file mode 100644
index 0000000..1cd647c
--- /dev/null
+++ b/usage/src/test/java/com/ning/billing/usage/timeline/samples/TestRepeatSample.java
@@ -0,0 +1,33 @@
+package com.ning.billing.usage.timeline.samples;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestRepeatSample {
+
+ @Test(groups = "fast")
+ public void testGetters() throws Exception {
+ final int repeatCount = 5;
+ final ScalarSample<Short> scalarSample = new ScalarSample<Short>(SampleOpcode.SHORT, (short) 12);
+ final RepeatSample<Short> repeatSample = new RepeatSample<Short>(repeatCount, scalarSample);
+
+ Assert.assertEquals(repeatSample.getRepeatCount(), repeatCount);
+ Assert.assertEquals(repeatSample.getSampleRepeated(), scalarSample);
+ Assert.assertEquals(repeatSample.getOpcode(), scalarSample.getOpcode());
+ }
+
+ @Test(groups = "fast")
+ public void testEquals() throws Exception {
+ final int repeatCount = 5;
+ final ScalarSample<Short> scalarSample = new ScalarSample<Short>(SampleOpcode.SHORT, (short) 12);
+
+ final RepeatSample<Short> repeatSample = new RepeatSample<Short>(repeatCount, scalarSample);
+ Assert.assertEquals(repeatSample, repeatSample);
+
+ final RepeatSample<Short> sameRepeatSample = new RepeatSample<Short>(repeatCount, scalarSample);
+ Assert.assertEquals(sameRepeatSample, repeatSample);
+
+ final RepeatSample<Short> otherRepeatSample = new RepeatSample<Short>(repeatCount + 1, scalarSample);
+ Assert.assertNotEquals(otherRepeatSample, repeatSample);
+ }
+}
diff --git a/usage/src/test/java/com/ning/billing/usage/timeline/samples/TestScalarSample.java b/usage/src/test/java/com/ning/billing/usage/timeline/samples/TestScalarSample.java
new file mode 100644
index 0000000..98626a3
--- /dev/null
+++ b/usage/src/test/java/com/ning/billing/usage/timeline/samples/TestScalarSample.java
@@ -0,0 +1,59 @@
+package com.ning.billing.usage.timeline.samples;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestScalarSample {
+
+ @Test(groups = "fast")
+ public void testGetters() throws Exception {
+ final SampleOpcode opcode = SampleOpcode.SHORT;
+ final short value = (short) 5;
+ final ScalarSample<Short> scalarSample = new ScalarSample<Short>(opcode, value);
+
+ Assert.assertEquals(scalarSample.getOpcode(), opcode);
+ Assert.assertEquals((short) scalarSample.getSampleValue(), value);
+ }
+
+ @Test(groups = "fast")
+ public void testEquals() throws Exception {
+ final SampleOpcode opcode = SampleOpcode.SHORT;
+ final short value = (short) 5;
+
+ final ScalarSample<Short> scalarSample = new ScalarSample<Short>(opcode, value);
+ Assert.assertEquals(scalarSample, scalarSample);
+
+ final ScalarSample<Short> sameScalarSample = new ScalarSample<Short>(opcode, value);
+ Assert.assertEquals(sameScalarSample, scalarSample);
+
+ final ScalarSample<Short> otherScalarSample = new ScalarSample<Short>(opcode, (short) (value + 1));
+ Assert.assertNotEquals(otherScalarSample, scalarSample);
+ }
+
+ @Test(groups = "fast")
+ public void testFromObject() throws Exception {
+ verifyFromObject(null, 0.0, null, SampleOpcode.NULL);
+
+ verifyFromObject((byte) 1, (double) 1, 1, SampleOpcode.BYTE);
+
+ verifyFromObject((short) 128, (double) 128, (short) 128, SampleOpcode.SHORT);
+ verifyFromObject(32767, (double) 32767, (short) 32767, SampleOpcode.SHORT);
+
+ verifyFromObject(32768, (double) 32768, 32768, SampleOpcode.INT);
+ verifyFromObject((long) 32767, (double) 32767, (short) 32767, SampleOpcode.SHORT);
+ verifyFromObject((long) 32768, (double) 32768, 32768, SampleOpcode.INT);
+
+ verifyFromObject(2147483648L, (double) 2147483648L, 2147483648L, SampleOpcode.LONG);
+
+ verifyFromObject((float) 1.3, 1.3, (float) 1.3, SampleOpcode.FLOAT);
+
+ verifyFromObject(12.24, 12.24, 12.24, SampleOpcode.DOUBLE);
+ }
+
+ private void verifyFromObject(final Object value, final double expectedDoubleValue, final Object expectedSampleValue, final SampleOpcode expectedSampleOpcode) {
+ final ScalarSample scalarSample = ScalarSample.fromObject(value);
+ Assert.assertEquals(scalarSample.getOpcode(), expectedSampleOpcode);
+ Assert.assertEquals(scalarSample.getSampleValue(), expectedSampleValue);
+ Assert.assertEquals(scalarSample.getDoubleValue(), expectedDoubleValue);
+ }
+}