/*
* Copyright (C) 2015, 2016 higherfrequencytrading.com
* Copyright (C) 2016 Roman Leventov
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.openhft.chronicle.values;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.MethodSpec;
import net.openhft.chronicle.bytes.Byteable;
import static java.lang.String.format;
import static net.openhft.chronicle.values.IntegerFieldModel.NORMAL_ACCESS_TYPE;
import static net.openhft.chronicle.values.Utils.capitalize;
final class PointerFieldModel extends IntegerBackedFieldModel {
private final ValueFieldModel pointedModel;
PointerFieldModel(ValueFieldModel pointedModel) {
this.pointedModel = pointedModel;
}
@Override
void postProcess() {
super.postProcess();
pointedModel.postProcess();
backend.type = long.class;
backend.range = RangeImpl.DEFAULT_LONG_RANGE;
backend.postProcess();
}
@Override
void checkState() {
super.checkState();
pointedModel.checkState();
}
private FieldSpec cachedValue() {
return pointedModel.nativeGenerator().cachedValue;
}
private void initCachedValue(
ValueBuilder valueBuilder, MethodSpec.Builder methodBuilder, String address) {
methodBuilder.addStatement("$N.bytesStore($N, $N, $L)", cachedValue(),
valueBuilder.bytesStoreForPointers(), address, pointedModel.sizeInBytes());
}
private String extractAddress(MethodSpec.Builder methodBuilder, String value) {
String addressVariable = value + "Address";
methodBuilder.addStatement("long $N", addressVariable);
methodBuilder.beginControlFlow("if ($N != null)", value);
{
methodBuilder.beginControlFlow("if (!($N instanceof $T))", value, Byteable.class);
String message =
"\"$N should be instance of $T, \" + $N.getClass() + \" is given\"";
methodBuilder.addStatement("throw new $T(" + message + ")",
IllegalArgumentException.class, name, Byteable.class, value);
methodBuilder.endControlFlow();
methodBuilder.addStatement(
"$N = (($T) $N).bytesStore().address((($T) $N).offset())",
addressVariable, Byteable.class, value, Byteable.class, value);
}
methodBuilder.nextControlFlow("else");
{
methodBuilder.addStatement("$N = 0L", addressVariable);
}
methodBuilder.endControlFlow();
return addressVariable;
}
private void genWriteMarshallable(
ValueBuilder valueBuilder, MethodSpec.Builder methodBuilder,
String address, Object value) {
String addressVariable = name + "Address";
methodBuilder.addStatement("long $N = $N", addressVariable, address);
methodBuilder.beginControlFlow("if ($N != 0)", addressVariable);
{
initCachedValue(valueBuilder, methodBuilder, address);
methodBuilder.addStatement("bytes.writeBoolean(true)");
methodBuilder.addStatement("$N.writeMarshallable(bytes)", value);
}
methodBuilder.nextControlFlow("else");
{
methodBuilder.addStatement("bytes.writeBoolean(false)");
}
methodBuilder.endControlFlow();
}
private void genReadMarshallable(
ValueBuilder valueBuilder, MethodSpec.Builder methodBuilder,
String address, Object value, Runnable setNull) {
String present = name + "Present";
methodBuilder.addStatement("boolean $N = bytes.readBoolean()", present);
methodBuilder.beginControlFlow("if ($N)", present);
{
String addressVariable = name + "Address";
methodBuilder.addStatement("long $N = $N", addressVariable, address);
methodBuilder.beginControlFlow("if ($N != 0)", addressVariable);
{
initCachedValue(valueBuilder, methodBuilder, address);
methodBuilder.addStatement("$N.readMarshallable(bytes)", value);
}
methodBuilder.nextControlFlow("else");
{
methodBuilder.addStatement("throw new $T($S)", IllegalStateException.class,
name + " field should be initialized to some pointer when reading " +
"non-null value from marshalled bytes");
}
methodBuilder.endControlFlow();
}
methodBuilder.nextControlFlow("else");
{
setNull.run();
}
methodBuilder.endControlFlow();
}
final MemberGenerator nativeGenerator = new IntegerBackedNativeMemberGenerator(this, backend) {
@Override
void generateFields(ValueBuilder valueBuilder) {
super.generateFields(valueBuilder);
pointedModel.nativeGenerator().generateFields(valueBuilder);
}
@Override
void generateArrayElementFields(
ArrayFieldModel arrayFieldModel, ValueBuilder valueBuilder) {
generateFields(valueBuilder);
}
@Override
void finishGet(
ValueBuilder valueBuilder, MethodSpec.Builder methodBuilder, String address) {
String addressVariable = name + "Address";
methodBuilder.addStatement("long $N = $N", addressVariable, address);
methodBuilder.beginControlFlow("if ($N != 0)", addressVariable);
{
initCachedValue(valueBuilder, methodBuilder, address);
methodBuilder.addStatement("return $N", cachedValue());
}
methodBuilder.nextControlFlow("else");
{
methodBuilder.addStatement("return null");
}
methodBuilder.endControlFlow();
}
@Override
String startSet(MethodSpec.Builder methodBuilder) {
return extractAddress(methodBuilder, varName());
}
@Override
void generateWriteMarshallable(
ValueBuilder valueBuilder, MethodSpec.Builder methodBuilder) {
String address = backingFieldModel.genGet(valueBuilder, NORMAL_ACCESS_TYPE);
genWriteMarshallable(valueBuilder, methodBuilder, address, cachedValue());
}
@Override
void generateReadMarshallable(ValueBuilder valueBuilder, MethodSpec.Builder methodBuilder) {
String address = backingFieldModel.genGet(valueBuilder, NORMAL_ACCESS_TYPE);
genReadMarshallable(valueBuilder, methodBuilder, address, cachedValue(), () ->
backingFieldModel.genSet(valueBuilder, methodBuilder, NORMAL_ACCESS_TYPE, "0L")
);
}
@Override
void generateArrayElementWriteMarshallable(
ArrayFieldModel arrayFieldModel, ValueBuilder valueBuilder,
MethodSpec.Builder methodBuilder) {
String address = backingFieldModel.genArrayElementGet(
arrayFieldModel, valueBuilder, methodBuilder, NORMAL_ACCESS_TYPE);
genWriteMarshallable(valueBuilder, methodBuilder, address, cachedValue());
}
@Override
void generateArrayElementReadMarshallable(
ArrayFieldModel arrayFieldModel,
ValueBuilder valueBuilder, MethodSpec.Builder methodBuilder) {
String address = backingFieldModel.genArrayElementGet(
arrayFieldModel, valueBuilder, methodBuilder, NORMAL_ACCESS_TYPE);
genReadMarshallable(valueBuilder, methodBuilder, address, cachedValue(),
() -> backingFieldModel.genArrayElementSet(
arrayFieldModel, valueBuilder, methodBuilder, NORMAL_ACCESS_TYPE, "0L")
);
}
@Override
void generateEquals(ValueBuilder valueBuilder, MethodSpec.Builder methodBuilder) {
String otherValueVariable = "other" + capitalize(name);
methodBuilder.addStatement("$T $N = other.$N()",
type, otherValueVariable, getOrGetVolatile().getName());
String otherAddress = extractAddress(methodBuilder, otherValueVariable);
String thisAddress = backingFieldModel.genGet(valueBuilder, NORMAL_ACCESS_TYPE);
methodBuilder.addCode("if ($N != $N) return false;\n", thisAddress, otherAddress);
}
@Override
void generateArrayElementEquals(
ArrayFieldModel arrayFieldModel, ValueBuilder valueBuilder,
MethodSpec.Builder methodBuilder) {
String otherValueVariable = "other" + capitalize(name);
methodBuilder.addStatement("$T $N = other.$N(index)",
type, otherValueVariable, getOrGetVolatile().getName());
String otherAddress = extractAddress(methodBuilder, otherValueVariable);
String thisAddress = backingFieldModel.genArrayElementGet(
arrayFieldModel, valueBuilder, methodBuilder, NORMAL_ACCESS_TYPE);
methodBuilder.addCode("if ($N != $N) return false;\n", thisAddress, otherAddress);
}
@Override
String generateHashCode(ValueBuilder valueBuilder, MethodSpec.Builder methodBuilder) {
String address = backingFieldModel.genGet(valueBuilder, NORMAL_ACCESS_TYPE);
return format("Long.hashCode(%s)", address);
}
@Override
String generateArrayElementHashCode(
ArrayFieldModel arrayFieldModel, ValueBuilder valueBuilder,
MethodSpec.Builder methodBuilder) {
String address = backingFieldModel.genArrayElementGet(
arrayFieldModel, valueBuilder, methodBuilder, NORMAL_ACCESS_TYPE);
return format("Long.hashCode(%s)", address);
}
};
@Override
MemberGenerator nativeGenerator() {
return nativeGenerator;
}
@Override
MemberGenerator createHeapGenerator() {
return new PrimitiveBackedHeapMemberGenerator(this, backend.type) {
@Override
void generateFields(ValueBuilder valueBuilder) {
super.generateFields(valueBuilder);
pointedModel.nativeGenerator().generateFields(valueBuilder);
}
@Override
void generateArrayElementFields(
ArrayFieldModel arrayFieldModel, ValueBuilder valueBuilder) {
super.generateArrayElementFields(arrayFieldModel, valueBuilder);
pointedModel.nativeGenerator()
.generateArrayElementFields(arrayFieldModel, valueBuilder);
}
@Override
String wrap(ValueBuilder valueBuilder, MethodSpec.Builder methodBuilder,
String rawStoredValue) {
String result = name + "Result";
methodBuilder.addStatement("$T $N", type, result);
methodBuilder.beginControlFlow("if ($N != 0)", rawStoredValue);
{
initCachedValue(valueBuilder, methodBuilder, rawStoredValue);
methodBuilder.addStatement("$N = $N", result, cachedValue());
}
methodBuilder.nextControlFlow("else");
{
methodBuilder.addStatement("$N = null", result);
}
methodBuilder.endControlFlow();
return result;
}
@Override
String unwrap(MethodSpec.Builder methodBuilder, String inputValue) {
return extractAddress(methodBuilder, inputValue);
}
private void genWriteMarshallable(
ValueBuilder valueBuilder, MethodSpec.Builder methodBuilder, String address) {
String addressVariable = name + "Address";
methodBuilder.addStatement("long $N = $N", addressVariable, address);
methodBuilder.beginControlFlow("if ($N != 0)", addressVariable);
{
initCachedValue(valueBuilder, methodBuilder, addressVariable);
methodBuilder.addStatement("bytes.writeBoolean(true)");
methodBuilder.addStatement("$N.writeMarshallable(bytes)", cachedValue());
}
methodBuilder.nextControlFlow("else");
{
methodBuilder.addStatement("bytes.writeBoolean(false)");
}
methodBuilder.endControlFlow();
}
@Override
void generateWriteMarshallable(
ValueBuilder valueBuilder, MethodSpec.Builder methodBuilder) {
genWriteMarshallable(valueBuilder, methodBuilder, field.name);
}
@Override
void generateArrayElementWriteMarshallable(
ArrayFieldModel arrayFieldModel, ValueBuilder valueBuilder,
MethodSpec.Builder methodBuilder) {
genWriteMarshallable(valueBuilder, methodBuilder, field.name + "[index]");
}
private void genReadMarshallable(
ValueBuilder valueBuilder, MethodSpec.Builder methodBuilder,
String address, Runnable setNull) {
String present = name + "Present";
methodBuilder.addStatement("boolean $N = bytes.readBoolean()", present);
methodBuilder.beginControlFlow("if ($N)", present);
{
String addressVariable = name + "Address";
methodBuilder.addStatement("long $N = $N", addressVariable, address);
methodBuilder.beginControlFlow("if ($N != 0)", addressVariable);
{
initCachedValue(valueBuilder, methodBuilder, addressVariable);
methodBuilder.addStatement("$N.readMarshallable(bytes)", cachedValue());
}
methodBuilder.nextControlFlow("else");
{
methodBuilder.addStatement("throw new $T($S)", IllegalStateException.class,
name + " field should be initialized to some pointer " +
"when reading non-null value from marshalled bytes");
}
methodBuilder.endControlFlow();
}
methodBuilder.nextControlFlow("else");
{
setNull.run();
}
methodBuilder.endControlFlow();
}
@Override
void generateReadMarshallable(
ValueBuilder valueBuilder, MethodSpec.Builder methodBuilder) {
genReadMarshallable(valueBuilder, methodBuilder, field.name,
() -> methodBuilder.addStatement("$N = 0L", field));
}
@Override
void generateArrayElementReadMarshallable(
ArrayFieldModel arrayFieldModel, ValueBuilder valueBuilder,
MethodSpec.Builder methodBuilder) {
genReadMarshallable(valueBuilder, methodBuilder, field.name + "[index]",
() -> methodBuilder.addStatement("$N[index] = 0L", field));
}
@Override
String generateHashCode(ValueBuilder valueBuilder, MethodSpec.Builder methodBuilder) {
return format("Long.hashCode(%s)", field.name);
}
@Override
String generateArrayElementHashCode(
ArrayFieldModel arrayFieldModel, ValueBuilder valueBuilder,
MethodSpec.Builder methodBuilder) {
return format("Long.hashCode(%s[index])", field.name);
}
};
}
}