com.manydesigns.elements.reflection.JavaClassAccessor Maven / Gradle / Ivy
/*
* Copyright (C) 2005-2019 ManyDesigns srl. All rights reserved.
* http://www.manydesigns.com/
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 3 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package com.manydesigns.elements.reflection;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.manydesigns.elements.annotations.Key;
import com.manydesigns.elements.util.ReflectionUtil;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* A {@link ClassAccessor} targeting Java classes. Use {@link GroovyClassAccessor} for Groovy classes instead.
*
* @author Paolo Predonzani - [email protected]
* @author Angelo Lupo - [email protected]
* @author Giampiero Granatella - [email protected]
* @author Alessio Stalla - [email protected]
*/
public class JavaClassAccessor implements ClassAccessor {
public static final String copyright =
"Copyright (C) 2005-2019 ManyDesigns srl";
//**************************************************************************
// Constants
//**************************************************************************
public final static String[] PROPERTY_NAME_BLACKLIST = {"class"};
//**************************************************************************
// Fields
//**************************************************************************
protected final Class javaClass;
protected final PropertyAccessor[] propertyAccessors;
protected final PropertyAccessor[] keyPropertyAccessors;
//**************************************************************************
// Static fields and methods
//**************************************************************************
protected static final Cache classAccessorCache;
public static final Logger logger =
LoggerFactory.getLogger(JavaClassAccessor.class);
static {
classAccessorCache = CacheBuilder.newBuilder().weakKeys().build();
}
public static JavaClassAccessor getClassAccessor(Class javaClass) {
JavaClassAccessor cachedResult = classAccessorCache.getIfPresent(javaClass);
if (cachedResult == null) {
logger.debug("Cache miss for: {}", javaClass);
cachedResult = new JavaClassAccessor(javaClass);
logger.debug("Caching key: {} - Value: {}",
javaClass, cachedResult);
classAccessorCache.put(javaClass, cachedResult);
} else {
logger.debug("Cache hit for: {} - Value: {}",
javaClass, cachedResult);
}
return cachedResult;
}
//**************************************************************************
// Constructors and initialization
//**************************************************************************
protected JavaClassAccessor(Class javaClass) {
this.javaClass = javaClass;
List accessorList = setupPropertyAccessors();
propertyAccessors = new PropertyAccessor[accessorList.size()];
accessorList.toArray(propertyAccessors);
List keyAccessors = setupKeyPropertyAccessors();
keyPropertyAccessors = new PropertyAccessor[keyAccessors.size()];
keyAccessors.toArray(keyPropertyAccessors);
}
protected List setupPropertyAccessors() {
List accessorList = new ArrayList();
// handle properties through introspection
try {
BeanInfo beanInfo = Introspector.getBeanInfo(javaClass);
PropertyDescriptor[] propertyDescriptors =
beanInfo.getPropertyDescriptors();
for (PropertyDescriptor current : propertyDescriptors) {
if(current.getReadMethod() == null) {
logger.debug("Skipping unreadable property {}", current.getName());
continue;
}
JavaPropertyAccessor accessor = new JavaPropertyAccessor(current);
if(isValidProperty(accessor)) {
accessorList.add(accessor);
}
}
} catch (IntrospectionException e) {
logger.error(e.getMessage(), e);
}
// handle public fields
for (Field field : javaClass.getFields()) {
if (isPropertyPresent(accessorList, field.getName())) {
continue;
}
JavaFieldAccessor accessor = new JavaFieldAccessor(field);
if(isValidProperty(accessor)) {
accessorList.add(accessor);
}
}
return accessorList;
}
protected boolean isValidProperty(PropertyAccessor propertyAccessor) {
// static field?
if (Modifier.isStatic(propertyAccessor.getModifiers())) {
return false;
}
// blacklisted?
if (ArrayUtils.contains(PROPERTY_NAME_BLACKLIST, propertyAccessor.getName())) {
return false;
}
return true;
}
protected List setupKeyPropertyAccessors() {
Map> keys = new HashMap>();
for(PropertyAccessor propertyAccessor : propertyAccessors) {
Key key = propertyAccessor.getAnnotation(Key.class);
if(key != null) {
List keyProperties = keys.get(key.name());
if(keyProperties == null) {
keyProperties = new ArrayList();
keys.put(key.name(), keyProperties);
}
keyProperties.add(propertyAccessor);
}
}
if(keys.isEmpty()) {
logger.debug("No primary key configured for {}", javaClass);
return Collections.emptyList();
}
String primaryKeyName;
List keyAccessors;
Key key = getAnnotation(Key.class);
if(key != null) {
primaryKeyName = key.name();
} else {
primaryKeyName = Key.DEFAULT_NAME;
}
keyAccessors = keys.get(primaryKeyName);
if(keyAccessors == null) {
keyAccessors = keys.get(Key.DEFAULT_NAME);
}
if(keyAccessors == null) {
logger.debug("Primary key \"" + primaryKeyName + "\" not found in " + javaClass + "; using the first available key.");
keyAccessors = keys.values().iterator().next();
}
Collections.sort(keyAccessors, (o1, o2) -> {
int ord1 = o1.getAnnotation(Key.class).order();
int ord2 = o2.getAnnotation(Key.class).order();
return Integer.compare(ord1, ord2);
});
return keyAccessors;
}
private boolean isPropertyPresent(List accessorList,
String name) {
for (PropertyAccessor current : accessorList) {
if (current.getName().equals(name)) {
return true;
}
}
return false;
}
//**************************************************************************
// ClassAccessor implementation
//**************************************************************************
public String getName() {
return javaClass.getName();
}
@Override
public Class> getType() {
return javaClass;
}
public PropertyAccessor getProperty(String propertyName)
throws NoSuchFieldException {
for (PropertyAccessor current : propertyAccessors) {
if (current.getName().equals(propertyName)) {
return current;
}
}
throw new NoSuchFieldException(propertyName);
}
public PropertyAccessor[] getProperties() {
return propertyAccessors.clone();
}
public PropertyAccessor[] getKeyProperties() {
return keyPropertyAccessors.clone();
}
public Object newInstance() {
return ReflectionUtil.newInstance(javaClass);
}
//**************************************************************************
// AnnotatedElement implementation
//**************************************************************************
public boolean isAnnotationPresent(Class extends Annotation> annotationClass) {
return javaClass.isAnnotationPresent(annotationClass);
}
public T getAnnotation(Class annotationClass) {
//noinspection unchecked
return (T) javaClass.getAnnotation(annotationClass);
}
public Annotation[] getAnnotations() {
return javaClass.getAnnotations();
}
public Annotation[] getDeclaredAnnotations() {
return javaClass.getDeclaredAnnotations();
}
//**************************************************************************
// Getters
//**************************************************************************
public Class getJavaClass() {
return javaClass;
}
//**************************************************************************
// Other methods
//**************************************************************************
@Override
public String toString() {
return new ToStringBuilder(this)
.append("javaClass", javaClass)
.toString();
}
}