Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
nl.talsmasoftware.reflection.beans.BeanReflectionSupport Maven / Gradle / Ivy
/*
* Copyright (C) 2016 Talsma ICT
*
* 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 nl.talsmasoftware.reflection.beans;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.lang.reflect.Modifier.isPublic;
import static java.lang.reflect.Modifier.isStatic;
import static java.util.Collections.*;
/**
* Support-class for bean reflection based on getter / setter methods and / or public field access for Java objects.
* Unfortunately this is necessary because the standard {@link Introspector bean introspector} does not return any
* public fields.
*
* Class diagram:
*
* @author Sjoerd Talsma
*/
public final class BeanReflectionSupport {
private static final Logger LOGGER = Logger.getLogger(BeanReflectionSupport.class.getName());
/**
* Cache, similar to the Introspector method cache.
*/
private static final Map, Reference>> reflectedPropertiesCache =
new WeakHashMap, Reference>>();
/**
* Private constructor to avoid instantiation of this class.
*/
private BeanReflectionSupport() {
throw new UnsupportedOperationException();
}
/**
* This method reflects the requested object and returns a map from property name to property instance.
* The map may not be modified again after it has been returned from this method; therefore an unmodifiable view of
* the map will be returned.
*
* @param source The object to be reflected.
* @return The unmodifiable map of reflected properties for the specified object.
*/
private static Map reflectedPropertiesOf(Object source) {
if (source == null) return emptyMap();
final Class> sourceType = source instanceof Class ? (Class) source : source.getClass();
Map properties = null;
synchronized (reflectedPropertiesCache) {
Reference> reference = reflectedPropertiesCache.get(sourceType);
if (reference != null) properties = reference.get();
}
if (properties == null) {
properties = reflectProperties(sourceType);
synchronized (reflectedPropertiesCache) {
reflectedPropertiesCache.put(sourceType,
new WeakReference>(properties));
}
}
return properties;
}
/**
* The bean reflection logic, not to be called directly but always through {@link #reflectedPropertiesOf(Object)}.
* That method provides important caching.
*
* @param sourceType The type to be reflected.
* @return The map with reflected properties for the specified type.
*/
private static Map reflectProperties(Class> sourceType) {
Map properties = new LinkedHashMap();
if (sourceType != null) {
addPublicFields(sourceType, properties);
addPropertyDescriptors(sourceType, properties);
}
return unmodifiable(properties);
}
/**
* Add all public fields for the sourceType
to the map of reflected properties.
*
* @param sourceType The source type to be reflected.
* @param properties The map of reflected properties to add public fields to.
*/
private static void addPublicFields(Class> sourceType, Map properties) {
for (Field field : sourceType.getFields()) {
if (isPublic(field.getModifiers()) && !isStatic(field.getModifiers())) {
properties.put(field.getName(), new ReflectedBeanProperty(null, field));
}
}
}
/**
* Add all {@link PropertyDescriptor property descriptors} from the {@link Introspector} to the map of reflected
* properties.
*
* @param sourceType The source type to be reflected.
* @param properties The map of reflected properties to add public fields to.
*/
private static void addPropertyDescriptors(Class> sourceType, Map properties) {
try { // java.beans.Introspector properties:
for (PropertyDescriptor descriptor : Introspector.getBeanInfo(sourceType).getPropertyDescriptors()) {
String name = descriptor.getName();
ReflectedBeanProperty property = properties.get(name);
if (property == null) properties.put(name, new ReflectedBeanProperty(descriptor, null));
else properties.put(name, property.withDescriptor(descriptor));
}
} catch (IntrospectionException is) {
LOGGER.log(Level.FINEST, "Could not reflect bean information of {0} because: {1}",
new Object[]{sourceType.getName(), is});
} catch (RuntimeException beanException) {
LOGGER.log(Level.FINEST, "Exception reflecting bean information of {0}: {1}",
new Object[]{sourceType.getName(), beanException});
}
}
/**
* This method flushes the caches of internally reflected information and asks the Bean {@link Introspector} to do
* the same.
*/
public static void flushCaches() {
synchronized (reflectedPropertiesCache) {
reflectedPropertiesCache.clear();
}
Introspector.flushCaches();
}
/**
* This method returns a property value for a specific object instance.
*
* @param bean The object instance to return the requested property value for.
* @param propertyName The name of the requested bean property.
* @return The value of the property or null
in case it could not be reflected.
*/
public static Object getPropertyValue(Object bean, String propertyName) {
Object propertyValue = null;
final ReflectedBeanProperty reflectedProperty = reflectedPropertiesOf(bean).get(propertyName);
if (reflectedProperty == null) {
LOGGER.log(Level.FINEST, "Property \"{0}\" not found in object: {1}", new Object[]{propertyName, bean});
} else {
propertyValue = reflectedProperty.read(bean);
}
return propertyValue;
}
/**
* This method writes the specified property value in an object instance.
*
* Obviously, the object should have a 'writable' property with the specified name.
*
* @param bean The object instance to set the property value for.
* @param propertyName The name of the (writable) bean property to be set.
* @param propertyValue The new property value to be set.
* @return true
if the property was succesfully set,
* or false
if this was not possibile for some reason.
*/
public static boolean setPropertyValue(Object bean, String propertyName, Object propertyValue) {
boolean result = false;
final ReflectedBeanProperty reflectedProperty = reflectedPropertiesOf(bean).get(propertyName);
if (reflectedProperty == null) {
LOGGER.log(Level.FINEST, "Property \"{0}\" not found in object: {1}", new Object[]{propertyName, bean});
} else {
result = reflectedProperty.write(bean, propertyValue);
}
return result;
}
/**
* This method returns the reflected properties for the specified bean.
*
* @param bean The object instance to reflect the bean properties of.
* @return The reflected bean properties.
*/
@SuppressWarnings("unchecked") // collection is read-only so this is safe.
public static Collection getBeanProperties(Object bean) {
final Collection extends BeanProperty> properties = reflectedPropertiesOf(bean).values();
return (Collection) properties;
}
/**
* This method returns all bean properties for the specified bean.
*
* @param bean The object instance to read the bean properties of.
* @return A map of all bean property names mapping to the corresponding property values.
*/
public static Map getPropertyValues(Object bean) {
Map propertyValues = new LinkedHashMap();
if (bean != null) for (BeanProperty property : getBeanProperties(bean)) {
if (property.isReadable()) propertyValues.put(property.getName(), property.read(bean));
}
return unmodifiable(propertyValues);
}
/**
* This method creates a new bean instance of the requested type using the default constructor and attempts to
* set all the specified property values.
*
* @param beanType The bean type to be instantiated using the default constructor.
* @param propertyValues The properties that have to be set.
* @param The bean type to be returned.
* @return The instantiated bean with the specified property values.
*/
public static T createBean(Class beanType, Map propertyValues) {
if (beanType == null) throw new IllegalArgumentException("Bean type was null.");
try {
T bean = beanType.getConstructor().newInstance();
if (propertyValues != null) for (Map.Entry propertyValue : propertyValues.entrySet()) {
setPropertyValue(bean, propertyValue.getKey(), propertyValue.getValue());
}
return bean;
} catch (NoSuchMethodException nsme) {
throw new IllegalArgumentException("Bean type " + beanType.getSimpleName() +
" does not have an accessible default constructor.", nsme);
} catch (InstantiationException ie) {
throw new IllegalArgumentException("Bean type " + beanType.getSimpleName() +
" could not be instantiated. Could it be that it is an abstract type?", ie);
} catch (IllegalAccessException iae) {
throw new IllegalArgumentException("Bean type " + beanType.getSimpleName() +
" does not have an accessible default constructor.", iae);
} catch (InvocationTargetException ite) {
throw ite.getCause() instanceof RuntimeException ? (RuntimeException) ite.getCause()
: new IllegalStateException("Exception occurred while creating a new instance of "
+ beanType.getSimpleName() + ": " + ite.getMessage(), ite.getCause());
}
}
/**
* Returns an unmodifiable view of the given map.
*
* @param map The map to be made unmodifiable.
* @param The type of the map keys.
* @param The type of the map values.
* @return An unmodifiable view of the given map.
*/
private static Map unmodifiable(Map map) {
switch (map.size()) {
case 0:
return emptyMap();
case 1:
final Map.Entry entry = map.entrySet().iterator().next();
return singletonMap(entry.getKey(), entry.getValue());
default:
return unmodifiableMap(map);
}
}
}