All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.openhft.chronicle.values.CodeTemplate Maven / Gradle / Ivy

There is a newer version: 2.27ea0
Show newest version
/*
 * Copyright 2016-2021 chronicle.software
 *
 *       https://chronicle.software
 *
 * 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 net.openhft.chronicle.values;

import net.openhft.chronicle.bytes.Byteable;
import net.openhft.chronicle.bytes.BytesMarshallable;

import java.io.Externalizable;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import static java.util.Arrays.asList;
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static net.openhft.chronicle.values.MethodTemplate.Type.ARRAY;
import static net.openhft.chronicle.values.MethodTemplate.Type.SCALAR;
import static net.openhft.chronicle.values.Primitives.isPrimitiveIntegerType;

enum CodeTemplate {
    ; // none

    public static final Function NO_ANNOTATED_PARAM = m -> null;
    static final List> NON_MODEL_TYPES = asList(
            Object.class, Serializable.class, Externalizable.class, BytesMarshallable.class,
            Copyable.class, Byteable.class);

    private static final SortedSet METHOD_TEMPLATES =
            new TreeSet<>(
                    comparing((MethodTemplate t) -> t.parameters)
                            .thenComparing(k -> -k.regex.length())
                            .thenComparing(k -> k.regex));
    private static final String FIELD_NAME = "([a-zA-Z_$][a-zA-Z\\d_$]*)";

    static {
        addReadPatterns("get", 0, FieldModel::setGet);
        addReadPatterns("", 0, FieldModel::setGet);
        addReadPatterns("is", 0, FieldModel::setGet);
        addReadPatterns("getVolatile", 0, FieldModel::setGetVolatile);
        addReadPatterns("getUsing", 1, FieldModel::setGetUsing);
        addWritePattern("set", 1, FieldModel::setSet);
        addWritePattern("", 1, FieldModel::setSet);
        addWritePattern("setVolatile", 1, FieldModel::setSetVolatile);
        addWritePattern("setOrdered", 1, FieldModel::setSetOrdered);
        addWritePattern("add", 1, FieldModel::setAdd);
        addWritePattern("addAtomic", 1, FieldModel::setAddAtomic);
        addWritePattern("compareAndSwap", 2, FieldModel::setCompareAndSwap);
    }

    private static void addReadPatterns(
            String regex, int arguments, BiConsumer addMethodToModel) {
        regex += FIELD_NAME;
        add(regex, arguments, SCALAR, Method::getReturnType, NO_ANNOTATED_PARAM, addMethodToModel);
        add(regex + "At", arguments + 1, ARRAY, Method::getReturnType, NO_ANNOTATED_PARAM,
                addMethodToModel);
    }

    public static void addWritePattern(
            String regex, int arguments, BiConsumer addMethodToModel) {
        regex += FIELD_NAME;
        add(regex, arguments, SCALAR,
                m -> m.getParameterTypes()[arguments - 1],
                m -> m.getParameters()[arguments - 1],
                addMethodToModel);
        add(regex + "At", arguments + 1, ARRAY,
                m -> m.getParameterTypes()[arguments],
                m -> m.getParameters()[arguments],
                addMethodToModel);
    }

    private static void add(
            String regex, int parameters, MethodTemplate.Type type,
            Function fieldType, Function annotatedParameter,
            BiConsumer addMethodToModel) {
        METHOD_TEMPLATES.add(new MethodTemplate(regex, parameters, type, fieldType,
                annotatedParameter, addMethodToModel));
    }

    static ValueModel createValueModel(Class valueType) {
        List fields = methodsAndTemplatesByField(valueType).entrySet().stream()
                .map(e -> createAndConfigureModel(e.getKey(), e.getValue())).collect(toList());
        if (fields.isEmpty())
            throw new IllegalArgumentException(valueType + " is not a value interface");
        fields.forEach(FieldModel::checkAnyWriteMethodPresent);
        fields.forEach(FieldModel::postProcess);
        fields.forEach(FieldModel::checkState);
        return new ValueModel(valueType, fields.stream());
    }

    private static FieldModel createAndConfigureModel(
            String fieldName, List methodsAndTemplates) {
        if (methodsAndTemplates.stream().map(mt -> mt.template.type).distinct().count() > 1) {
            throw new IllegalArgumentException("All or none accessors of the " + fieldName +
                    " field should end with -At (what means this is an array field)");
        }
        ScalarFieldModel scalarModel =
                createAndConfigureScalarModel(fieldName, methodsAndTemplates);
        if (methodsAndTemplates.get(0).template.type == SCALAR) {
            return scalarModel;
        } else {
            // If the field turns out to be an array field, @Align applied to the element model belongs to the array
            // model, and the element model is configured with Array.elementOffsetAlignment() and
            // Array.elementDontCrossAlignment().
            scalarModel.resetAlignment();
            ArrayFieldModel arrayModel = new ArrayFieldModel(scalarModel);
            configureModel(arrayModel, methodsAndTemplates);
            return arrayModel;
        }
    }

    private static ScalarFieldModel createAndConfigureScalarModel(
            String fieldName, List methodsAndTemplates) {
        ScalarFieldModel nonPointerModel =
                createNonPointerScalarModel(fieldName, methodsAndTemplates);
        configureModel(nonPointerModel, methodsAndTemplates);

        boolean hasPointerAnnotation = methodsAndTemplates.stream().map(mt -> mt.method)
                .flatMap(m -> Arrays.stream(m.getParameterAnnotations()).flatMap(Arrays::stream))
                .anyMatch(a -> a.annotationType() == Pointer.class);
        if (hasPointerAnnotation) {
            if (!(nonPointerModel instanceof ValueFieldModel)) {
                throw new IllegalStateException(fieldName + " annotated with @Pointer but has " +
                        nonPointerModel.type.getName() + " type which is not a value interface");
            }
            PointerFieldModel pointerModel =
                    new PointerFieldModel((ValueFieldModel) nonPointerModel);
            configureModel(pointerModel, methodsAndTemplates);
            return pointerModel;
        } else {
            return nonPointerModel;
        }
    }

    private static void configureModel(
            FieldModel model, List methodsAndTemplates) {
        methodsAndTemplates.forEach(mt -> {
            model.name = mt.fieldName;
            model.addInfo(mt.method, mt.template);
            mt.template.addMethodToModel.accept(model, mt.method);
        });
    }

    private static ScalarFieldModel createNonPointerScalarModel(
            String fieldName, List methodsAndTemplates) {
        // CharSequence fields could have a method void getUsing() which doesn't contain actual
        // field type info (String or CharSequence).
        MethodAndTemplate nonGetUsingMethodAndTemplate = methodsAndTemplates.stream()
                .filter(mt -> !mt.template.regex.startsWith("getUsing"))
                .findAny().orElseThrow(() -> new IllegalStateException(fieldName +
                        " field should have some accessor methods except " +
                        methodsAndTemplates.get(0).method.getName()));
        MethodTemplate nonGetUsingMethodTemplate = nonGetUsingMethodAndTemplate.template;
        Method nonGetUsingMethod = nonGetUsingMethodAndTemplate.method;
        Class fieldType = nonGetUsingMethodTemplate.fieldType.apply(nonGetUsingMethod);
        if (isPrimitiveIntegerType(fieldType))
            return new IntegerFieldModel();
        if (fieldType == float.class || fieldType == double.class)
            return new FloatingFieldModel();
        if (fieldType == boolean.class)
            return new BooleanFieldModel();
        if (Enum.class.isAssignableFrom(fieldType))
            return new EnumFieldModel();
        if (fieldType == Date.class)
            return new DateFieldModel();
        if (CharSequence.class.isAssignableFrom(fieldType))
            return new CharSequenceFieldModel();
        if (fieldType.isInterface())
            return new ValueFieldModel();
        throw new IllegalStateException(fieldName + " field type " + fieldType +
                " is not supported: not a primitive, enum, CharSequence " +
                "or another value interface");
    }

    private static Map> methodsAndTemplatesByField(
            Class valueType) {
        return Stream.of(valueType.getMethods())
                .filter(m -> (m.getModifiers() & Modifier.ABSTRACT) != 0)
                .filter(m -> NON_MODEL_TYPES.stream().noneMatch(t -> hasMethod(t, m)))
                .map(m -> {
                    MethodTemplate methodTemplate = METHOD_TEMPLATES.stream()
                            .filter(t -> t.parameters == m.getParameterCount())
                            .filter(t -> m.getName().matches(t.regex))
                            .findFirst().orElseThrow(IllegalStateException::new);
                    Matcher matcher = Pattern.compile(methodTemplate.regex)
                            .matcher(m.getName());
                    if (!matcher.find())
                        throw new AssertionError();
                    String fieldName = convertFieldName(matcher.group(1));
                    return new MethodAndTemplate(m, methodTemplate, fieldName);
                }).collect(groupingBy(mt -> mt.fieldName));
    }

    private static boolean hasMethod(Class type, Method m) {
        return Stream.of(type.getMethods())
                .anyMatch(m2 -> m2.getName().equals(m.getName()) &&
                        Arrays.equals(m2.getParameterTypes(), m.getParameterTypes()));
    }

    static String convertFieldName(String name) {
        if (name.length() > 1 && Character.isUpperCase(name.charAt(1))) return name;
        if (Character.isLowerCase(name.charAt(0))) return name;
        return Character.toLowerCase(name.charAt(0)) + name.substring(1);
    }

    static class MethodAndTemplate {
        final Method method;
        final MethodTemplate template;
        final String fieldName;

        MethodAndTemplate(Method method, MethodTemplate template, String fieldName) {
            this.method = method;
            this.template = template;
            this.fieldName = fieldName;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy