
io.activej.serializer.def.impl.ClassSerializerDef Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of activej-serializer Show documentation
Show all versions of activej-serializer Show documentation
Extremely fast and space-efficient serializers. Implemented using bytecode engineering.
/*
* Copyright (C) 2020 ActiveJ 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.activej.serializer.def.impl;
import io.activej.codegen.expression.Expression;
import io.activej.codegen.expression.Expressions;
import io.activej.codegen.expression.Variable;
import io.activej.common.annotation.ExposedInternals;
import io.activej.common.builder.AbstractBuilder;
import io.activej.serializer.CompatibilityLevel;
import io.activej.serializer.def.AbstractSerializerDef;
import io.activej.serializer.def.SerializerDef;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Type;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.UnaryOperator;
import static io.activej.codegen.expression.Expressions.*;
import static io.activej.common.Checks.checkArgument;
import static io.activej.common.Checks.checkNotNull;
import static io.activej.common.Utils.toHashMap;
import static java.lang.Character.toUpperCase;
import static java.lang.String.format;
import static java.lang.reflect.Modifier.*;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toSet;
import static org.objectweb.asm.Type.*;
@ExposedInternals
public final class ClassSerializerDef extends AbstractSerializerDef {
public final Class> encodeType;
public final Class> decodeType;
public record PropertyDef(Member member, SerializerDef serializer, String name, int versionAdded, int versionDeleted, Object defaultValue) {
public PropertyDef {
checkArgument(member instanceof Method || member instanceof Field, "Property %s must be either Method or Field", member);
checkNotNull(serializer);
checkNotNull(name);
checkArgument(versionAdded >= -1);
checkArgument(versionDeleted >= -1);
}
public PropertyDef(Member member, SerializerDef serializer) {
this(member, serializer, member instanceof Method method ? stripGet(method.getName(), method.getReturnType()) : ((Field) member).getName());
}
public PropertyDef(Member member, SerializerDef serializer, String name) {
this(member, serializer, name, -1, -1, null);
}
public boolean hasVersion(int version) {
if (versionAdded == -1 && versionDeleted == -1) {
return true;
}
if (versionAdded != -1 && versionDeleted == -1) {
return version >= versionAdded;
}
if (versionAdded == -1) {
return version < versionDeleted;
}
if (versionAdded > versionDeleted) {
return version < versionDeleted || version >= versionAdded;
}
if (versionAdded < versionDeleted) {
return version >= versionAdded && version < versionDeleted;
}
throw new IllegalStateException("Added and deleted versions are equal");
}
public Class> getRawType() {
if (member instanceof Field field)
return field.getType();
if (member instanceof Method method)
return method.getReturnType();
throw new AssertionError();
}
public Expression defaultExpression() {
if (defaultValue != null) return Expressions.value(defaultValue, getRawType());
Type asmType = getType(getRawType());
return switch (asmType.getSort()) {
case BOOLEAN -> value(false);
case CHAR -> value((char) 0);
case BYTE -> value((byte) 0);
case SHORT -> value((short) 0);
case INT -> value(0);
case Type.LONG -> value(0L);
case Type.FLOAT -> value(0f);
case Type.DOUBLE -> value(0d);
case ARRAY, OBJECT -> nullRef(asmType);
default -> throw new IllegalArgumentException("Unsupported type " + asmType);
};
}
}
public final List properties;
public record SetterDef(Method method, List properties) {}
public final List setters;
public record FactoryDef(Executable member, List properties) {}
public @Nullable FactoryDef factory;
public ClassSerializerDef(
Class> encodeType, Class> decodeType,
List properties, List setters, @Nullable FactoryDef factory
) {
var propertyNames = properties.stream().map(PropertyDef::name).collect(toSet());
checkArgument(setters.stream().allMatch(setter -> propertyNames.containsAll(setter.properties)));
checkArgument(factory == null || propertyNames.containsAll(factory.properties));
this.encodeType = encodeType;
this.decodeType = decodeType;
this.properties = properties;
this.setters = setters;
this.factory = factory;
}
public static Builder builder(Class> type) {
return builder(type, type);
}
public static Builder builder(Class> encodeType, Class> decodeType) {
checkArgument(!decodeType.isInterface(), "Cannot serialize an interface " + decodeType.getName());
checkArgument(encodeType.isAssignableFrom(decodeType), format("Class %s should be assignable from %s", encodeType, decodeType));
return new ClassSerializerDef(encodeType, decodeType, new ArrayList<>(), new ArrayList<>(), null).new Builder();
}
public final class Builder extends AbstractBuilder {
private Builder() {}
public Builder withField(Field field, SerializerDef serializer, int added, int removed) {
checkNotBuilt(this);
checkArgument(isPublic(field.getModifiers()), "Field %s should be public", field);
ClassSerializerDef.this.properties.removeIf(property ->
property.member instanceof Field f &&
Objects.equals(f.getName(), field.getName()));
String name = field.getName();
PropertyDef propertyDef = new PropertyDef(field, serializer, name, added, removed, null);
return withPropertyDef(propertyDef);
}
public Builder withGetter(Method method, SerializerDef serializer, int added, int removed) {
checkNotBuilt(this);
checkArgument(method.getGenericParameterTypes().length == 0, "Method %s should have 0 generic parameter types", method);
checkArgument(isPublic(method.getModifiers()), "Method %s should be public", method);
ClassSerializerDef.this.properties.removeIf(property ->
property.member instanceof Method m &&
Objects.equals(m.getName(), method.getName()) &&
Arrays.equals(m.getParameterTypes(), method.getParameterTypes()));
String name = stripGet(method.getName(), method.getReturnType());
return withPropertyDef(new PropertyDef(method, serializer, name, added, removed, null));
}
public Builder withPropertyDef(PropertyDef propertyDef) {
properties.add(propertyDef);
return this;
}
public Builder withSetter(Method method, List properties) {
checkNotBuilt(this);
checkArgument(!isPrivate(method.getModifiers()), format("Setter cannot be private: %s", method));
checkArgument(method.getGenericParameterTypes().length == properties.size(),
"Number of arguments of a method %s should match a size of list of properties (%d)", method, properties.size());
ClassSerializerDef.this.setters.removeIf(setter ->
Objects.equals(setter.method.getName(), method.getName()) &&
Arrays.equals(setter.method.getParameterTypes(), method.getParameterTypes()));
return withSetterDef(new SetterDef(method, properties));
}
public Builder withSetterDef(SetterDef setterDef) {
ClassSerializerDef.this.setters.add(setterDef);
return this;
}
public Builder withConstructor(Constructor> constructor, List properties) {
checkNotBuilt(this);
checkArgument(!isPrivate(constructor.getModifiers()), format("Constructor cannot be private: %s", constructor));
checkArgument(constructor.getGenericParameterTypes().length == properties.size(),
"Number of arguments of a constructor %s should match a size of list of properties (%d)", constructor, properties.size());
return withFactoryDef(new FactoryDef(constructor, properties));
}
public Builder withStaticFactoryMethod(Method staticFactoryMethod, List properties) {
checkNotBuilt(this);
checkArgument(!isPrivate(staticFactoryMethod.getModifiers()), format("Factory cannot be private: %s", staticFactoryMethod));
checkArgument(isStatic(staticFactoryMethod.getModifiers()), format("Factory must be static: %s", staticFactoryMethod));
checkArgument(staticFactoryMethod.getGenericParameterTypes().length == properties.size(),
"Number of arguments of a method %s should match a size of list of properties (%d)", staticFactoryMethod, properties.size());
return withFactoryDef(new FactoryDef(staticFactoryMethod, properties));
}
public Builder withFactoryDef(FactoryDef factoryDef) {
ClassSerializerDef.this.factory = factoryDef;
return this;
}
public Builder withMatchingSetters() {
checkNotBuilt(this);
Set usedProperties = new HashSet<>();
if (factory != null) {
usedProperties.addAll(factory.properties);
}
for (var setter : setters) {
usedProperties.addAll(setter.properties);
}
for (var property : properties) {
if (!(property.member instanceof Method getter)) continue;
if (usedProperties.contains(property.name)) continue;
String setterName = "set" + toUpperCase(property.name.charAt(0)) + property.name.substring(1);
try {
Method setter = decodeType.getMethod(setterName, getter.getReturnType());
if (!isPrivate(setter.getModifiers())) {
withSetter(setter, List.of(property.name));
}
} catch (NoSuchMethodException e) {
throw new IllegalArgumentException(e);
}
}
return this;
}
@Override
protected ClassSerializerDef doBuild() {
return ClassSerializerDef.this;
}
}
@Override
public void accept(Visitor visitor) {
for (var property : properties) {
visitor.visit(property.name, property.serializer);
}
}
@Override
public Set getVersions() {
Set versions = new HashSet<>();
for (var property : properties) {
if (property.versionAdded != -1) {
versions.add(property.versionAdded);
}
if (property.versionDeleted != -1) {
versions.add(property.versionDeleted);
}
}
return versions;
}
@Override
public boolean isInline(int version, CompatibilityLevel compatibilityLevel) {
return false;// fields.size() <= 1;
}
@Override
public Class> getEncodeType() {
return encodeType;
}
@Override
public Class> getDecodeType() {
return decodeType;
}
@Override
public Expression encode(StaticEncoders staticEncoders, Expression buf, Variable pos, Expression value, int version, CompatibilityLevel compatibilityLevel) {
List list = new ArrayList<>();
for (var property : properties) {
if (!property.hasVersion(version)) continue;
Encoder encoder = property.serializer.defineEncoder(staticEncoders, version, compatibilityLevel);
Expression expression;
Class> fieldType = property.serializer.getEncodeType();
if (property.member instanceof Field field) {
expression = encoder.encode(buf, pos, cast(property(value, field.getName()), fieldType));
} else if (property.member instanceof Method method) {
expression = encoder.encode(buf, pos, cast(call(value, method.getName()), fieldType));
} else {
throw new AssertionError();
}
list.add(expression);
}
return sequence(list);
}
@Override
public Expression decode(StaticDecoders staticDecoders, Expression in, int version, CompatibilityLevel compatibilityLevel) {
return decoder(staticDecoders, in, version, compatibilityLevel, value -> sequence());
}
public Expression decoder(StaticDecoders staticDecoders, Expression in, int version, CompatibilityLevel compatibilityLevel, UnaryOperator instanceInitializer) {
Map propertyMap = properties.stream().collect(toHashMap(p -> p.name, identity()));
return let(
propertyExpressions -> {
for (PropertyDef propertyDef : properties) {
if (!propertyDef.hasVersion(version)) continue;
propertyExpressions.add(
propertyDef.serializer.defineDecoder(staticDecoders, version, compatibilityLevel).decode(in));
}
},
propertyValues -> {
Map propertyValuesMap = new HashMap<>();
int i = 0;
for (var propertyDef : properties) {
if (!propertyDef.hasVersion(version)) continue;
propertyValuesMap.put(propertyDef.name, propertyValues[i++]);
}
return let(
factory == null ?
constructor(decodeType) :
factory.member instanceof Constructor ?
constructor(decodeType,
extractParameters(factory.member, factory.properties, version, propertyMap, propertyValuesMap)) :
staticCall(factory.member.getDeclaringClass(), factory.member.getName(),
extractParameters(factory.member, factory.properties, version, propertyMap, propertyValuesMap)),
instance -> sequence(seq -> {
seq.add(instanceInitializer.apply(instance));
for (var setter : setters) {
if (setter.properties.stream().map(propertyMap::get).noneMatch(propertyDef -> propertyDef.hasVersion(version))) {
continue;
}
seq.add(
call(instance, setter.method.getName(),
extractParameters(setter.method, setter.properties, version, propertyMap, propertyValuesMap)));
}
for (var propertyDef : properties) {
if (!propertyDef.hasVersion(version)) continue;
if (!(propertyDef.member instanceof Field field)) continue;
if (isFinal(field.getModifiers())) continue;
seq.add(
set(property(instance, field.getName()),
cast(propertyValuesMap.get(propertyDef.name), propertyDef.getRawType())));
}
return instance;
}));
});
}
private static Expression[] extractParameters(
Executable executable, List properties, int version,
Map propertyMap, Map propertyValuesMap
) {
Expression[] parameters = new Expression[properties.size()];
for (int i = 0; i < properties.size(); i++) {
String property = properties.get(i);
PropertyDef propertyDef = propertyMap.get(property);
if (propertyDef == null)
throw new NullPointerException(format("Field '%s' is not found in '%s'", property, executable));
if (propertyDef.hasVersion(version)) {
parameters[i] = cast(propertyValuesMap.get(property), executable.getParameterTypes()[i]);
} else {
parameters[i] = cast(propertyDef.defaultExpression(), executable.getParameterTypes()[i]);
}
}
return parameters;
}
private static String stripGet(String getterName, Class> type) {
if (type == Boolean.TYPE || type == BooleanSerializerDef.class) {
if (getterName.startsWith("is") && getterName.length() > 2) {
return Character.toLowerCase(getterName.charAt(2)) + getterName.substring(3);
}
}
if (getterName.startsWith("get") && getterName.length() > 3) {
return Character.toLowerCase(getterName.charAt(3)) + getterName.substring(4);
}
return getterName;
}
@Override
public String toString() {
return "ClassSerializerDef{" + encodeType.getSimpleName() + '}';
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy