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

com.blackbuild.klum.ast.util.DslHelper Maven / Gradle / Ivy

There is a newer version: 2.0.0-rc.39
Show newest version
/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015-2024 Stephan Pauxberger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.blackbuild.klum.ast.util;

import com.blackbuild.groovy.configdsl.transform.DSL;
import com.blackbuild.groovy.configdsl.transform.FieldType;
import com.blackbuild.groovy.configdsl.transform.Key;
import groovy.lang.*;
import groovyjarjarasm.asm.Opcodes;
import org.codehaus.groovy.reflection.CachedField;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.jetbrains.annotations.NotNull;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.lang.String.format;

public class DslHelper {

    public static final Class FIELD_ANNOTATION = com.blackbuild.groovy.configdsl.transform.Field.class;

    private DslHelper() {}

    public static boolean isDslType(Type type) {
        if (!(type instanceof Class))
            return false;
        Class clazz = (Class) type;
        return clazz.isAnnotationPresent(DSL.class);
    }

    public static Type requireDslType(Type type) {
        if (!isDslType(type))
            throw new IllegalArgumentException(format("Type %s is not a DSL type", type));
        return type;
    }

    public static boolean isDslObject(Object object) {
        return object != null && isDslType(object.getClass());
    }

    public static List> getDslHierarchyOf(Class type) {
        List> result = new ArrayList<>();
        while (isDslType(type)) {
            result.add(0, type);
            type = type.getSuperclass();
        }
        return result;
    }

    public static List> getHierarchyOf(Class type) {
        List> result = new ArrayList<>();
        while (type != null) {
            result.add(type);
            type = type.getSuperclass();
        }
        return result;
    }

    public static Type getElementType(Class type, String name) {
        Optional field = getField(type, name);
        return field.stream().map(DslHelper::getElementType).findFirst().orElseThrow(() -> new MissingFieldException(name, type));
    }

    public static Type getElementType(Field field) {
        Type genericType = field.getGenericType();
        ParameterizedType parameterizedType = (ParameterizedType) genericType;
        Type[] typeArguments = parameterizedType.getActualTypeArguments();
        Type typeArgument = typeArguments[typeArguments.length - 1];
        if (typeArgument instanceof WildcardType)
            return ((WildcardType) typeArgument).getUpperBounds()[0];
        return typeArgument;
    }

    @SuppressWarnings("unchecked")
    public static  T castTo(Object object, Class targetType) {
        return (T) InvokerHelper.invokeMethod(object, "asType", targetType);
    }

    public static Optional getField(Class type, String name) {
        return getHierarchyOf(type).stream()
                .map(layer -> getFieldOfHierarchyLayer(layer, name))
                .filter(Optional::isPresent)
                .map(Optional::get)
                .findFirst();
    }

    public static Optional getCachedField(Class type, String name) {
        return getHierarchyOf(type).stream()
                .map(layer -> getCachedFieldOfHierarchyLayer(layer, name))
                .filter(Optional::isPresent)
                .map(Optional::get)
                .findFirst();
    }

    public static FieldType getKlumFieldType(Field field) {
        com.blackbuild.groovy.configdsl.transform.Field fieldAnnotation = field.getAnnotation(com.blackbuild.groovy.configdsl.transform.Field.class);
        if (fieldAnnotation == null) return FieldType.DEFAULT;
        return fieldAnnotation.value();
    }

    public static  T getFieldAnnotation(Class type, String fieldName, Class annotationType) {
        Optional field = getField(type, fieldName);
        if (!field.isPresent())
            throw new IllegalArgumentException(format("Type %s does not have a field named %s", type, fieldName));
        return field.get().getAnnotation(annotationType);
    }

    public static boolean isClosure(Class type) {
        return Closure.class.isAssignableFrom(type);
    }

    public static  Optional getOptionalFieldAnnotation(Class type, String fieldName, Class annotationType) {
        return getField(type, fieldName)
                .map(value -> value.getAnnotation(annotationType));
    }

    public static Optional getMethod(Class type, String name, Class... args) {
        return getHierarchyOf(type).stream()
                .map(layer -> getMethodOfHierarchyLayer(layer, name, args))
                .filter(Optional::isPresent)
                .map(Optional::get)
                .findFirst();
    }

    public static Optional getFieldOrMethod(Class type, String name, Class argumentType) {
        Optional field = getField(type, name);
        if (field.isPresent())
            return field;

        return getMethod(type, name, argumentType);
    }

    private static Optional getFieldOfHierarchyLayer(Class layer, String name) {
        return getCachedFieldOfHierarchyLayer(layer, name).map(DslHelper::getRealField);
    }

    private static Optional getCachedFieldOfHierarchyLayer(Class layer, String name) {
        MetaProperty metaProperty = InvokerHelper.getMetaClass(layer).getMetaProperty(name);
        if (!(metaProperty instanceof MetaBeanProperty)) return Optional.empty();

        return Optional.ofNullable(((MetaBeanProperty) metaProperty).getField());
    }

    // groovy 3 makes Field.field private, so we need a workaround
    private static Field getRealField(CachedField cachedField) {
        return cachedField != null ? (Field) InvokerHelper.getAttribute(cachedField, "field") : null;
    }

    private static Optional getMethodOfHierarchyLayer(Class layer, String name, Class[] args) {
        try {
            return Optional.of(layer.getMethod(name,args));
        } catch (NoSuchMethodException e) {
            return Optional.empty();
        }
    }

    public static Optional getKeyField(Class type) {
        return getFieldsAnnotatedWith(type, Key.class).findFirst();
    }

    public static boolean isKeyed(Class type) {
        return getKeyField(type).isPresent();
    }

    public static  Class requireKeyed(Class type) {
        if (!isKeyed(type))
            throw new IllegalArgumentException(format("Type %s is not keyed.", type));
        return type;
    }

    public static  Class requireNotKeyed(Class type) {
        if (isKeyed(type))
            throw new IllegalArgumentException(format("Type %s is keyed.", type));
        return type;
    }

    public static List> getRwHierarchyOf(Class rwType) {
        List> result = new ArrayList<>();
        while (rwType.getEnclosingClass() != null) {
            result.add(rwType);
            rwType = rwType.getSuperclass();
        }
        Collections.reverse(result);
        return result;
    }

    public static boolean isInstantiable(Class type) {
        return !type.isInterface() && ((type.getModifiers() & Opcodes.ACC_ABSTRACT) == 0);
    }

    public static Stream getMethodsAnnotatedWith(Class type, Class annotation) {

        return getDslOrRwHierarchyOf(type)
                .stream()
                .map(Class::getDeclaredMethods)
                .flatMap(array -> Arrays.stream(array).sorted(Comparator.comparing(Method::getName)))
                .filter(method -> method.isAnnotationPresent(annotation));
    }

    @NotNull
    private static List> getDslOrRwHierarchyOf(Class type) {
        return type.isAnnotationPresent(DSL.class) ? getDslHierarchyOf(type) : getRwHierarchyOf(type);
    }

    public static Stream getFieldsAnnotatedWith(Class type, Class annotation) {

        return getDslOrRwHierarchyOf(type)
                .stream()
                .map(Class::getDeclaredFields)
                .flatMap(Arrays::stream)
                .filter(field -> field.isAnnotationPresent(annotation));
    }

    public static  Optional getVirtualSetter(Class rwType, String methodName, Class type) {
        List methods = getMethodsAnnotatedWith(rwType, FIELD_ANNOTATION)
                .filter(method -> method.getName().equals(methodName))
                .filter(method -> method.getParameterTypes()[0].isAssignableFrom(type))
                .collect(Collectors.toList());

        if (methods.isEmpty())
            return Optional.empty();

        if (methods.size() == 1)
            return Optional.of(methods.get(0));

        throw new IllegalStateException(format("Found more than one virtual setter matching %s(%s): %s", methodName, type.getName(), methods));
    }

    static Object getAttributeValue(String name, Object instance) {
        Optional cachedField = getCachedField(instance.getClass(), name);

        // cannot use .map, because value can be null
        if (cachedField.isPresent())
            return cachedField.get().getProperty(instance);

        throw new MissingPropertyException(name, instance.getClass());
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy