/*
* Copyright (C) 2015 SoftIndex LLC.
*
* 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 io.datakernel.serializer.asm;
import io.datakernel.bytebuf.SerializationUtils;
import io.datakernel.codegen.Expression;
import io.datakernel.codegen.ForVar;
import io.datakernel.codegen.Variable;
import io.datakernel.serializer.CompatibilityLevel;
import io.datakernel.serializer.NullableOptimization;
import io.datakernel.serializer.SerializerBuilder;
import java.util.HashMap;
import java.util.Map;
import static io.datakernel.codegen.Expressions.*;
import static io.datakernel.codegen.utils.Preconditions.check;
import static io.datakernel.codegen.utils.Preconditions.checkNotNull;
@SuppressWarnings("PointlessArithmeticExpression")
public final class SerializerGenHppcMap implements SerializerGen, NullableOptimization {
private static Map<Class<?>, SerializerGen> primitiveSerializers = new HashMap<Class<?>, SerializerGen>() {{
put(byte.class, new SerializerGenByte());
put(short.class, new SerializerGenShort());
put(int.class, new SerializerGenInt(true));
put(long.class, new SerializerGenLong(false));
put(float.class, new SerializerGenFloat());
put(double.class, new SerializerGenDouble());
put(char.class, new SerializerGenChar());
}};
private static String toUpperCamel(String s) {
StringBuilder sb = new StringBuilder();
sb.append(Character.toUpperCase(s.charAt(0)));
sb.append(s.substring(1));
return sb.toString();
}
public static SerializerGenBuilder serializerGenBuilder(final Class<?> mapType, final Class<?> keyType, final Class<?> valueType) {
String prefix = toUpperCamel(keyType.getSimpleName()) + toUpperCamel(valueType.getSimpleName());
check(mapType.getSimpleName().startsWith(prefix), "Expected mapType '%s', but was begin '%s'", mapType.getSimpleName(), prefix);
return new SerializerGenBuilder() {
@Override
public SerializerGen serializer(Class<?> type, final SerializerForType[] generics, SerializerGen fallback) {
SerializerGen keySerializer;
SerializerGen valueSerializer;
if (generics.length == 2) {
check((keyType == Object.class) && (valueType == Object.class), "keyType and valueType must be Object.class");
keySerializer = generics[0].serializer;
valueSerializer = generics[1].serializer;
} else if (generics.length == 1) {
check((keyType == Object.class) || (valueType == Object.class), "keyType or valueType must be Object.class");
if (keyType == Object.class) {
keySerializer = generics[0].serializer;
valueSerializer = primitiveSerializers.get(valueType);
} else {
keySerializer = primitiveSerializers.get(keyType);
valueSerializer = generics[0].serializer;
}
} else {
keySerializer = primitiveSerializers.get(keyType);
valueSerializer = primitiveSerializers.get(valueType);
}
return new SerializerGenHppcMap(mapType, keyType, valueType, checkNotNull(keySerializer), checkNotNull(valueSerializer));
}
};
}
private final Class<?> mapType;
private final Class<?> hashMapType;
private final Class<?> iteratorType;
private final Class<?> keyType;
private final Class<?> valueType;
private final SerializerGen keySerializer;
private final SerializerGen valueSerializer;
private final boolean nullable;
private SerializerGenHppcMap(Class<?> mapType, Class<?> keyType, Class<?> valueType, SerializerGen keySerializer, SerializerGen valueSerializer, boolean nullable) {
this.mapType = mapType;
this.keyType = keyType;
this.valueType = valueType;
this.keySerializer = keySerializer;
this.valueSerializer = valueSerializer;
this.nullable = nullable;
try {
String prefix = toUpperCamel(keyType.getSimpleName()) + toUpperCamel(valueType.getSimpleName());
this.iteratorType = Class.forName("com.carrotsearch.hppc.cursors." + prefix + "Cursor");
this.hashMapType = Class.forName("com.carrotsearch.hppc." + prefix + "OpenHashMap");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
private SerializerGenHppcMap(Class<?> mapType, Class<?> keyType, Class<?> valueType, SerializerGen keySerializer, SerializerGen valueSerializer) {
this(mapType, keyType, valueType, keySerializer, valueSerializer, false);
}
@Override
public void getVersions(VersionsCollector versions) {
versions.addRecursive(valueSerializer);
}
@Override
public boolean isInline() {
return true;
}
@Override
public Class<?> getRawType() throws RuntimeException {
return mapType;
}
@Override
public void prepareSerializeStaticMethods(int version, SerializerBuilder.StaticMethods staticMethods, CompatibilityLevel compatibilityLevel) {
keySerializer.prepareSerializeStaticMethods(version, staticMethods, compatibilityLevel);
valueSerializer.prepareSerializeStaticMethods(version, staticMethods, compatibilityLevel);
}
@Override
public Expression serialize(final Expression byteArray, final Variable off, Expression value, final int version, final SerializerBuilder.StaticMethods staticMethods, final CompatibilityLevel compatibilityLevel) {
Expression size = call(value, "size");
Expression length = set(off, callStatic(SerializationUtils.class, "writeVarInt", byteArray, off, (!nullable ? size : inc(size))));
Expression hppcMapForEach = hppcMapForEach(iteratorType, value,
new ForVar() {
@Override
public Expression forVar(Expression it) { return set(off, keySerializer.serialize(byteArray, off, cast(it, keySerializer.getRawType()), version, staticMethods, compatibilityLevel)); }
},
new ForVar() {
@Override
public Expression forVar(Expression it) {
return set(off, valueSerializer.serialize(byteArray, off, cast(it, valueSerializer.getRawType()), version, staticMethods, compatibilityLevel));
}
});
if (!nullable) {
return sequence(length, hppcMapForEach, off);
} else {
return ifThenElse(isNull(value),
sequence(set(off, callStatic(SerializationUtils.class, "writeVarInt", byteArray, off, value(0))), off),
sequence(length, hppcMapForEach, off));
}
}
@Override
public void prepareDeserializeStaticMethods(int version, SerializerBuilder.StaticMethods staticMethods, CompatibilityLevel compatibilityLevel) {
keySerializer.prepareDeserializeStaticMethods(version, staticMethods, compatibilityLevel);
valueSerializer.prepareDeserializeStaticMethods(version, staticMethods, compatibilityLevel);
}
@Override
public Expression deserialize(Class<?> targetType, final int version, final SerializerBuilder.StaticMethods staticMethods, final CompatibilityLevel compatibilityLevel) {
Expression length = let(call(arg(0), "readVarInt"));
final Expression map = let(constructor(hashMapType));
final Class<?> valueType = valueSerializer.getRawType();
final Class<?> keyType = keySerializer.getRawType();
Expression expressionFor = expressionFor((!nullable ? length : dec(length)), new ForVar() {
@Override
public Expression forVar(Expression it) {
return sequence(call(map, "put",
cast(keySerializer.deserialize(keyType, version, staticMethods, compatibilityLevel), SerializerGenHppcMap.this.keyType),
cast(valueSerializer.deserialize(valueType, version, staticMethods, compatibilityLevel), SerializerGenHppcMap.this.valueType)
), voidExp()
);
}
});
if (!nullable) {
return sequence(length, map, expressionFor, map);
} else {
return ifThenElse(cmpEq(length, value(0)),
nullRef(hashMapType),
sequence(length, map, expressionFor, map));
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
SerializerGenHppcMap that = (SerializerGenHppcMap) o;
if (nullable != that.nullable) return false;
if (mapType != null ? !mapType.equals(that.mapType) : that.mapType != null) return false;
if (hashMapType != null ? !hashMapType.equals(that.hashMapType) : that.hashMapType != null) return false;
if (iteratorType != null ? !iteratorType.equals(that.iteratorType) : that.iteratorType != null) return false;
if (keyType != null ? !keyType.equals(that.keyType) : that.keyType != null) return false;
if (valueType != null ? !valueType.equals(that.valueType) : that.valueType != null) return false;
if (keySerializer != null ? !keySerializer.equals(that.keySerializer) : that.keySerializer != null)
return false;
return !(valueSerializer != null ? !valueSerializer.equals(that.valueSerializer) : that.valueSerializer != null);
}
@Override
public int hashCode() {
int result = mapType != null ? mapType.hashCode() : 0;
result = 31 * result + (hashMapType != null ? hashMapType.hashCode() : 0);
result = 31 * result + (iteratorType != null ? iteratorType.hashCode() : 0);
result = 31 * result + (keyType != null ? keyType.hashCode() : 0);
result = 31 * result + (valueType != null ? valueType.hashCode() : 0);
result = 31 * result + (keySerializer != null ? keySerializer.hashCode() : 0);
result = 31 * result + (valueSerializer != null ? valueSerializer.hashCode() : 0);
result = 31 * result + (nullable ? 1 : 0);
return result;
}
@Override
public SerializerGen setNullable() {
return new SerializerGenHppcMap(mapType, keyType, valueType, keySerializer, valueSerializer, true);
}
}