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

net.e6tech.elements.common.reflection.Annotated Maven / Gradle / Ivy

There is a newer version: 2.7.9
Show newest version
/*
 * Copyright 2017 Futeh Kao
 *
 * 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.e6tech.elements.common.reflection;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import net.e6tech.elements.common.resources.Provision;
import net.e6tech.elements.common.util.SystemException;
import net.e6tech.elements.common.util.datastructure.Pair;

import java.beans.BeanInfo;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
import java.util.function.Function;

/**
 * This class is used to extract Java bean methods or fields that are annotated.
 */
@SuppressWarnings("unchecked")
public class Annotated {
    private static final MethodHandles.Lookup lookup = MethodHandles.lookup();

    private static LoadingCache, Class>, Annotated> annotatedCache = CacheBuilder.newBuilder()
            .maximumSize(1000)
            .initialCapacity(100)
            .concurrencyLevel(Provision.cacheBuilderConcurrencyLevel)
            .expireAfterWrite(120 * 60 * 1000L, TimeUnit.MILLISECONDS)
            .build(new CacheLoader, Class>, Annotated>() {
                public Annotated load(Pair, Class> pair)  {
                    return new Annotated(pair.key(), pair.value());
                }
            });

    private Class annotationClass;
    private List> entries = new ArrayList<>();
    private Cache, Lookup> lookups = CacheBuilder.newBuilder()
            .concurrencyLevel(Provision.cacheBuilderConcurrencyLevel)
            .maximumSize(1000)
            .initialCapacity(16)
            .expireAfterWrite(120 * 60 * 1000L, TimeUnit.MILLISECONDS)
            .build();

    public Annotated(Class clazz, Class annotationClass) {
        this.annotationClass = annotationClass;
        properties(clazz);
        fields(clazz);
        entries = Collections.unmodifiableList(entries);
    }

    public static  Accessor accessor(Object target, Class annotationClass, Function function, Class valueType) {
        Lookup lookup = lookup(target.getClass(), annotationClass, function, valueType);
        return lookup.accessor(target);
    }

    public static  Lookup lookup(Class clazz, Class annotationClass, Function function, Class valueType) {
        try {
            return annotatedCache.get(new Pair<>(clazz, annotationClass)).lookup(function, valueType);
        } catch (ExecutionException e) {
            throw new SystemException(e.getCause());
        }
    }

    public  Lookup lookup(Function function, Class valueType) {
        MyInvocationHandler handler = new MyInvocationHandler();
        A proxy = (A) Proxy.newProxyInstance(annotationClass.getClassLoader(), new Class[]{ annotationClass }, handler);
        function.apply(proxy);
        Method method = handler.method;
        if (method == null) {
            throw new IllegalArgumentException("Null annotation method for " + annotationClass);
        }
        try {
            return (Lookup) lookups.get(new Pair<>(method.getName(), valueType), () -> new Lookup<>(this, method, valueType));
        } catch (ExecutionException e) {
            throw new SystemException(e.getCause());
        }
    }

    private void fields(Class clazz) {
        Class cls = clazz;
        while (cls != Object.class) {
            Field[] fields = cls.getDeclaredFields();
            for (Field field : fields) {
                A e = field.getDeclaredAnnotation(annotationClass);
                if (e != null) {
                    entries.add(new Entry(e, field));
                }
            }
            cls = cls.getSuperclass();
        }
    }

    private void properties(Class clazz) {
        BeanInfo beanInfo = Reflection.getBeanInfo(clazz);
        for (PropertyDescriptor prop : beanInfo.getPropertyDescriptors()) {
            Annotation e = null;
            if (prop.getWriteMethod() != null)
                e = prop.getWriteMethod().getDeclaredAnnotation(annotationClass);

            if (e == null && prop.getReadMethod() != null)
                e = prop.getReadMethod().getDeclaredAnnotation(annotationClass);

            if (e != null)
                entries.add(new Entry(e, prop.getName(), prop.getReadMethod(), prop.getWriteMethod()));
        }
    }

    public static class Entry {
        private String name;
        private MethodHandle setter;
        private MethodHandle getter;
        private Function lambdaGetter;
        private BiConsumer lambdaSetter;
        private A annotation;
        private Type type;
        private Class rawType;

        Entry(A annotation, String name, Method getter, Method setter) {
            this.annotation = annotation;
            this.name= name;
            try {
                if (setter != null) {
                    this.setter = lookup.unreflect(setter);
                    type = setter.getGenericParameterTypes()[0];
                    rawType = setter.getParameterTypes()[0];
                    lambdaSetter = Lambda.reflectSetter(lookup, setter);
                }

                if (getter != null) {
                    this.getter = lookup.unreflect(getter);
                    type = getter.getGenericReturnType();
                    rawType = getter.getReturnType();
                    lambdaGetter = Lambda.reflectGetter(lookup, getter);
                }

            } catch (Exception e) {
                throw new SystemException(e);
            }
        }

        Entry(A annotation, Field field) {
            this.annotation = annotation;
            this.name = field.getName();
            this.type = field.getGenericType();
            this.rawType = field.getType();
            if (!Modifier.isPublic(field.getModifiers()))
                field.setAccessible(true);

            try {
                this.setter = lookup.unreflectSetter(field);
                this.getter = lookup.unreflectGetter(field);
            } catch (Exception e) {
                throw new SystemException(e);
            }
        }

        public Object get(Object target) {

            if (lambdaGetter != null) {
                try {
                    return lambdaGetter.apply(target);
                } catch (NoClassDefFoundError ex) {
                    lambdaGetter = null;
                }
            }

            if (getter != null) {
                try {
                    return getter.invoke(target);
                } catch (Throwable throwable) {
                    // ignore
                }
            }
            return null;
        }

        public void set(Object target, Object value) {
            if (lambdaSetter != null) {
                try {
                    lambdaSetter.accept(target, value);
                    return;
                } catch (NoClassDefFoundError e) {
                    lambdaSetter = null;
                }
            }

            if (setter != null) {
                try {
                    setter.invoke(target, value);
                } catch (Throwable throwable) {
                    // ignore
                }
            }
        }

        public String getName() {
            return name;
        }

        public  T getAnnotation() {
            return (T) annotation;
        }

        public Type getType() {
            return type;
        }

        public Class getRawType() {
            return rawType;
        }
    }

    public static class Lookup {
        private Map>> annotationValues;
        private List> entries = new ArrayList<>();

        Lookup(Annotated annotated, Method method, Class valueClass) {
            annotationValues = new HashMap<>();
            for (Entry e : annotated.entries) {
                if (valueClass != null && !valueClass.isAssignableFrom(e.getRawType()))
                    continue;
                E value = null;
                try {
                    value = (E) method.invoke(e.annotation);
                } catch (Exception e1) {
                    // ignored
                }
                if (value != null) {
                    List> list = annotationValues.computeIfAbsent(value, key2 -> new ArrayList<>());
                    list.add(e);
                    entries.add(e);
                }
            }
            entries = Collections.unmodifiableList(entries);
            annotationValues = Collections.unmodifiableMap(annotationValues);
        }

        public List> entries() {
            return entries;
        }

        public Map>> annotationValues() {
            return annotationValues;
        }

        public Map>> find(E ... searchValues) {
            Map>> result = new HashMap<>();
            if (searchValues != null) {
                for (E searchValue : searchValues) {
                    List> found = annotationValues.get(searchValue);
                    if (found != null)
                        result.put(searchValue, found);
                }
            } else {
                result.putAll(annotationValues);
            }
            return result;
        }

        public Accessor accessor(Object target) {
            return new Accessor<>(this, target);
        }
    }

    public static class Accessor {
        private Object target;
        private Lookup lookup;

        Accessor(Lookup lookup, Object target) {
            this.lookup = lookup;
            this.target = target;
        }

        public V get(E annotatedValue) {
            Map>> result = lookup.find(annotatedValue);
            List> list = result.get(annotatedValue);
            if (list != null)
                return (V) list.get(0).get(target);
            return null;
        }

        public Map get(E ... annotatedValues) {
            Map map = new HashMap<>();
            Map>> result = lookup.find(annotatedValues);
            for (Map.Entry>> e : result.entrySet()) {
                map.put(e.getKey(), (V) e.getValue().get(0).get(target));
            }
            return map;
        }

        public Map getAll() {
            Map map = new HashMap<>();
            for (Map.Entry>> e : lookup.annotationValues().entrySet()) {
                map.put(e.getKey(), (V) e.getValue().get(0).get(target));
            }
            return map;
        }

        public Accessor set(E annotatedValue, Object value) {
            Map>> result = lookup.find(annotatedValue);
            List> list = result.get(annotatedValue);
            if (list != null && !list.isEmpty()) {
                list.get(0).set(target, value);
            }
            return this;
        }
    }

    private static class MyInvocationHandler implements InvocationHandler {
        Method method;
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (Annotator.objectMethods.containsKey(method.getName())
                    && method.getParameterCount() == Annotator.objectMethods.get(method.getName())) {
                // skip
            } else {
                this.method = method;
            }

            if (method.getReturnType().isPrimitive()) {
                return Primitives.defaultValue(method.getReturnType());
            } else {
                return null;
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy