/* * Copyright 2014 astamuse company,Ltd. * * 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 com.astamuse.asta4d.util.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.astamuse.asta4d.Configuration; import com.astamuse.asta4d.util.ClassUtil; public class AnnotatedPropertyUtil { private static class ReadOnlyAnnotatedPropertyInfo extends AnnotatedPropertyInfo { private AnnotatedPropertyInfo info; ReadOnlyAnnotatedPropertyInfo(AnnotatedPropertyInfo info) { this.info = info; } public String getName() { return info.getName(); } public void setName(String name) { throw new UnsupportedOperationException(); } public String getBeanPropertyName() { return info.getBeanPropertyName(); } public void setBeanPropertyName(String beanPropertyName) { throw new UnsupportedOperationException(); } public Field getField() { return info.getField(); } public void setField(Field field) { throw new UnsupportedOperationException(); } public Method getGetter() { return info.getGetter(); } public void setGetter(Method getter) { throw new UnsupportedOperationException(); } public Method getSetter() { return info.getSetter(); } public void setSetter(Method setter) { throw new UnsupportedOperationException(); } @SuppressWarnings("rawtypes") public Class getType() { return info.getType(); } @SuppressWarnings("rawtypes") public void setType(Class type) { throw new UnsupportedOperationException(); } public <A extends Annotation> A getAnnotation(Class<A> annotationCls) { return info.getAnnotation(annotationCls); } public void setAnnotations(List<Annotation> annotationList) { throw new UnsupportedOperationException(); } public void assignValue(Object instance, Object value) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { info.assignValue(instance, value); } public Object retrieveValue(Object instance) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { return info.retrieveValue(instance); } public int hashCode() { return info.hashCode(); } public boolean equals(Object obj) { return info.equals(obj); } public String toString() { return info.toString(); } } private static class AnnotatedPropertyInfoMap { Map<String, List<AnnotatedPropertyInfo>> nameMap; Map<String, List<AnnotatedPropertyInfo>> beanNameMap; List<AnnotatedPropertyInfo> list; AnnotatedPropertyInfoMap(List<AnnotatedPropertyInfo> infoList) { list = infoList.stream().map(info -> new ReadOnlyAnnotatedPropertyInfo(info)).collect(Collectors.toList()); list = Collections.unmodifiableList(list); nameMap = list.stream().collect(Collectors.groupingBy(info -> info.getName())); makeListUnmodifiable(nameMap); nameMap = Collections.unmodifiableMap(nameMap); beanNameMap = list.stream().collect(Collectors.groupingBy(info -> info.getBeanPropertyName())); makeListUnmodifiable(beanNameMap); beanNameMap = Collections.unmodifiableMap(beanNameMap); } void makeListUnmodifiable(Map<String, List<AnnotatedPropertyInfo>> map) { for (String key : map.keySet()) { map.put(key, Collections.unmodifiableList(map.get(key))); } } } private static final Logger logger = LoggerFactory.getLogger(AnnotatedPropertyUtil.class); private static final ConcurrentHashMap<String, AnnotatedPropertyInfoMap> propertiesMapCache = new ConcurrentHashMap<>(); // TODO allow method property to override field property to avoid duplicated properties @SuppressWarnings({ "rawtypes", "unchecked" }) private static AnnotatedPropertyInfoMap retrievePropertiesMap(Class cls) { String cacheKey = cls.getName(); AnnotatedPropertyInfoMap map = propertiesMapCache.get(cacheKey); if (map == null) { List<AnnotatedPropertyInfo> infoList = new LinkedList<>(); Set<String> beanPropertyNameSet = new HashSet<>(); Method[] mtds = cls.getMethods(); for (Method method : mtds) { List<Annotation> annoList = ConvertableAnnotationRetriever.retrieveAnnotationHierarchyList(AnnotatedProperty.class, method.getAnnotations()); if (CollectionUtils.isEmpty(annoList)) { continue; } AnnotatedPropertyInfo info = new AnnotatedPropertyInfo(); info.setAnnotations(annoList); boolean isGet = false; boolean isSet = false; String propertySuffixe = method.getName(); if (propertySuffixe.startsWith("set")) { propertySuffixe = propertySuffixe.substring(3); isSet = true; } else if (propertySuffixe.startsWith("get")) { propertySuffixe = propertySuffixe.substring(3); isGet = true; } else if (propertySuffixe.startsWith("is")) { propertySuffixe = propertySuffixe.substring(2); isSet = true; } else { String msg = String.format("Method [%s]:[%s] can not be treated as a getter or setter method.", cls.getName(), method.toGenericString()); throw new RuntimeException(msg); } char[] cs = propertySuffixe.toCharArray(); cs[0] = Character.toLowerCase(cs[0]); info.setBeanPropertyName(new String(cs)); AnnotatedProperty ap = (AnnotatedProperty) annoList.get(0);// must by String name = ap.name(); if (StringUtils.isEmpty(name)) { name = info.getBeanPropertyName(); } info.setName(name); if (isGet) { info.setGetter(method); info.setType(method.getReturnType()); String setterName = "set" + propertySuffixe; Method setter = null; try { setter = cls.getMethod(setterName, method.getReturnType()); } catch (NoSuchMethodException | SecurityException e) { String msg = "Could not find setter method:[{}({})] in class[{}] for annotated getter:[{}]"; logger.warn(msg, new Object[] { setterName, method.getReturnType().getName(), cls.getName(), method.getName() }); } info.setSetter(setter); } if (isSet) { info.setSetter(method); info.setType(method.getParameterTypes()[0]); String getterName = "get" + propertySuffixe; Method getter = null; try { getter = cls.getMethod(getterName); } catch (NoSuchMethodException | SecurityException e) { String msg = "Could not find getter method:[{}:{}] in class[{}] for annotated setter:[{}]"; logger.warn(msg, new Object[] { getterName, method.getReturnType().getName(), cls.getName(), method.getName() }); } info.setGetter(getter); } infoList.add(info); beanPropertyNameSet.add(info.getBeanPropertyName()); } List<Field> list = new ArrayList<>(ClassUtil.retrieveAllFieldsIncludeAllSuperClasses(cls)); Iterator<Field> it = list.iterator(); while (it.hasNext()) { Field f = it.next(); List<Annotation> annoList = ConvertableAnnotationRetriever.retrieveAnnotationHierarchyList(AnnotatedProperty.class, f.getAnnotations()); if (CollectionUtils.isNotEmpty(annoList)) { AnnotatedProperty ap = (AnnotatedProperty) annoList.get(0);// must by String beanPropertyName = f.getName(); if (beanPropertyNameSet.contains(beanPropertyName)) { continue; } String name = ap.name(); if (StringUtils.isEmpty(name)) { name = f.getName(); } AnnotatedPropertyInfo info = new AnnotatedPropertyInfo(); info.setAnnotations(annoList); info.setBeanPropertyName(beanPropertyName); info.setName(name); info.setField(f); info.setGetter(null); info.setSetter(null); info.setType(f.getType()); infoList.add(info); } } map = new AnnotatedPropertyInfoMap(infoList); if (Configuration.getConfiguration().isCacheEnable()) { propertiesMapCache.put(cacheKey, map); } } return map; } @SuppressWarnings("rawtypes") public static List<AnnotatedPropertyInfo> retrieveProperties(Class cls) { AnnotatedPropertyInfoMap map = retrievePropertiesMap(cls); return map.list; } @SuppressWarnings("rawtypes") public static List<AnnotatedPropertyInfo> retrievePropertyByName(Class cls, final String name) { AnnotatedPropertyInfoMap map = retrievePropertiesMap(cls); return map.nameMap.get(name); } @SuppressWarnings("rawtypes") public static List<AnnotatedPropertyInfo> retrievePropertyByBeanPropertyName(Class cls, final String name) { AnnotatedPropertyInfoMap map = retrievePropertiesMap(cls); return map.beanNameMap.get(name); } public static void assignValueByName(Object instance, String name, Object value) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { List<AnnotatedPropertyInfo> list = retrievePropertyByName(instance.getClass(), name); assignValue(list, instance, value); } public static void assignValueByBeanPropertyName(Object instance, String name, Object value) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { List<AnnotatedPropertyInfo> list = retrievePropertyByBeanPropertyName(instance.getClass(), name); assignValue(list, instance, value); } public static void assignValue(List<AnnotatedPropertyInfo> list, Object instance, Object value) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException { for (AnnotatedPropertyInfo p : list) { p.assignValue(instance, value); } } }