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.
org.httprpc.beans.BeanAdapter Maven / Gradle / Ivy
/*
* 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 org.httprpc.beans;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalAmount;
import java.util.AbstractList;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
/**
* {@link Map} adapter for Java bean types.
*/
public class BeanAdapter extends AbstractMap {
/**
* Class representing a bean property.
*/
public static class Property {
private Method accessor = null;
private List mutators = new LinkedList<>();
private Property() {
}
/**
* Returns the property's accessor.
*
* @return
* The property's accessor, or null
if no accessor is defined.
*/
public Method getAccessor() {
return accessor;
}
/**
* Returns the property's mutators.
*
* @return
* The property's mutators.
*/
public List getMutators() {
return Collections.unmodifiableList(mutators);
}
}
// Iterable adapter
private static class IterableAdapter extends AbstractList {
Iterable> iterable;
Map, Map> propertyCache;
IterableAdapter(Iterable> iterable, Map, Map> propertyCache) {
this.iterable = iterable;
this.propertyCache = propertyCache;
}
@Override
public Object get(int index) {
return adapt(getList().get(index), propertyCache);
}
@Override
public int size() {
return getList().size();
}
@SuppressWarnings("unchecked")
private List getList() {
if (!(iterable instanceof List>)) {
throw new UnsupportedOperationException();
}
return (List)iterable;
}
@Override
public Iterator iterator() {
return new Iterator() {
private Iterator> iterator = iterable.iterator();
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Object next() {
return adapt(iterator.next(), propertyCache);
}
};
}
}
// Map adapter
private static class MapAdapter extends AbstractMap {
Map, ?> map;
Map, Map> propertyCache;
MapAdapter(Map, ?> map, Map, Map> propertyCache) {
this.map = map;
this.propertyCache = propertyCache;
}
@Override
public Object get(Object key) {
return adapt(map.get(key), propertyCache);
}
@Override
public Set> entrySet() {
return new AbstractSet>() {
@Override
public int size() {
return map.size();
}
@Override
public Iterator> iterator() {
return new Iterator>() {
private Iterator extends Entry, ?>> iterator = map.entrySet().iterator();
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Entry next() {
return new Entry() {
private Entry, ?> entry = iterator.next();
@Override
public Object getKey() {
return entry.getKey();
}
@Override
public Object getValue() {
return adapt(entry.getValue(), propertyCache);
}
@Override
public Object setValue(Object value) {
throw new UnsupportedOperationException();
}
};
}
};
}
};
}
}
// Typed invocation handler
private static class TypedInvocationHandler implements InvocationHandler {
Map, ?> map;
TypedInvocationHandler(Map, ?> map) {
this.map = map;
}
@Override
public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
if (method.getDeclaringClass() == Object.class) {
return method.invoke(this, arguments);
} else {
String key = getKey(method);
if (key == null || method.getParameterCount() > 0) {
throw new UnsupportedOperationException();
}
return coerce(map.get(key), method.getGenericReturnType());
}
}
@Override
public int hashCode() {
return map.hashCode();
}
@Override
public boolean equals(Object object) {
if (object instanceof Proxy) {
object = Proxy.getInvocationHandler(object);
}
if (!(object instanceof TypedInvocationHandler)) {
return false;
}
return map.equals(((TypedInvocationHandler)object).map);
}
@Override
public String toString() {
return map.toString();
}
}
private Object bean;
private Map, Map> propertyCache;
private Map properties;
private static final String GET_PREFIX = "get";
private static final String IS_PREFIX = "is";
private static final String SET_PREFIX = "set";
/**
* Constructs a new bean adapter.
*
* @param bean
* The source bean.
*/
public BeanAdapter(Object bean) {
this(bean, new HashMap<>());
}
private BeanAdapter(Object bean, Map, Map> propertyCache) {
if (bean == null) {
throw new IllegalArgumentException();
}
this.bean = bean;
this.propertyCache = propertyCache;
Class> type = bean.getClass();
if (Proxy.class.isAssignableFrom(type)) {
Class>[] interfaces = type.getInterfaces();
if (interfaces.length == 0) {
throw new IllegalArgumentException();
}
type = interfaces[0];
}
properties = propertyCache.get(type);
if (properties == null) {
properties = getProperties(type);
propertyCache.put(type, properties);
}
}
@Override
public Object get(Object key) {
if (key == null) {
throw new IllegalArgumentException();
}
Property property = properties.get(key);
Object value;
if (property != null && property.accessor != null) {
try {
value = adapt(property.accessor.invoke(bean), propertyCache);
} catch (IllegalAccessException | InvocationTargetException exception) {
throw new RuntimeException(exception);
}
} else {
value = null;
}
return value;
}
@Override
@SuppressWarnings("java:S1905")
public Object put(String key, Object value) {
Property property = properties.get(key);
if (property == null) {
throw new UnsupportedOperationException();
}
int i = 0;
for (Method mutator : property.mutators) {
try {
mutator.invoke(bean, (Object)coerce(value, mutator.getGenericParameterTypes()[0]));
} catch (Exception exception) {
i++;
}
}
if (i == property.mutators.size()) {
throw new UnsupportedOperationException();
}
return null;
}
@Override
public Set> entrySet() {
return new AbstractSet>() {
@Override
public int size() {
return properties.size();
}
@Override
public Iterator> iterator() {
return new Iterator>() {
private Iterator> iterator = properties.entrySet().iterator();
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Entry next() {
Entry entry = iterator.next();
Object value;
try {
Property property = entry.getValue();
if (property.accessor != null) {
value = adapt(property.accessor.invoke(bean), propertyCache);
} else {
value = null;
}
} catch (IllegalAccessException | InvocationTargetException exception) {
throw new RuntimeException(exception);
}
return new SimpleImmutableEntry<>(entry.getKey(), value);
}
};
}
};
}
/**
* Adapts a value for loose typing. If the value is null
* or an instance of one of the following types, it is returned as is:
*
*
* {@link CharSequence}
* {@link Number}
* {@link Boolean}
* {@link Enum}
* {@link Date}
* {@link TemporalAccessor}
* {@link TemporalAmount}
* {@link UUID}
* {@link URL}
*
*
* If the value is an {@link Iterable}, it is wrapped in an adapter that will
* recursively adapt the iterable's elements. If the value is a {@link Map},
* it is wrapped in an adapter that will recursively adapt the map's values.
* Otherwise, the value is assumed to be a bean and is wrapped in a
* {@link BeanAdapter}.
*
* @param
* The target type.
*
* @param value
* The value to adapt.
*
* @return
* The adapted value.
*/
@SuppressWarnings("unchecked")
public static T adapt(Object value) {
return (T)adapt(value, new HashMap<>());
}
private static Object adapt(Object value, Map, Map> propertyCache) {
if (value == null
|| value instanceof CharSequence
|| value instanceof Number
|| value instanceof Boolean
|| value instanceof Enum>
|| value instanceof Date
|| value instanceof TemporalAccessor
|| value instanceof TemporalAmount
|| value instanceof UUID
|| value instanceof URL) {
return value;
} else if (value instanceof Iterable>) {
return new IterableAdapter((Iterable>)value, propertyCache);
} else if (value instanceof Map, ?>) {
return new MapAdapter((Map, ?>)value, propertyCache);
} else {
return new BeanAdapter(value, propertyCache);
}
}
/**
* Coerces a value to a given type.
*
* For class types, if the value is already an instance of the requested
* type, it is returned as is.
*
* Otherwise, if the requested type is one of the following, the return
* value is obtained via an appropriate conversion method; for example,
* {@link Number#intValue()}, {@link Object#toString()}, or
* {@link LocalDate#parse(CharSequence)}:
*
*
* {@link Byte} or byte
* {@link Short} or short
* {@link Integer} or int
* {@link Long} or long
* {@link Float} or float
* {@link Double} or double
* {@link Boolean} or boolean
* {@link String}
* {@link Date}
* {@link Instant}
* {@link LocalDate}
* {@link LocalTime}
* {@link LocalDateTime}
* {@link Duration}
* {@link Period}
* {@link UUID}
* {@link URL}
*
*
* If the target type is an {@link Enum}, the resulting value is the first
* constant whose string representation matches the value's string
* representation.
*
* If none of the previous apply, the target type is assumed to be a bean.
* The provided value is assumed to be a map and is converted as follows:
*
*
* If the target type is an interface, the return value is a proxy
* implementation of the interface that maps accessor methods to entries in
* the map. {@link Object} methods are delegated to the underlying map.
* If the target type is a concrete class, an instance of the type is
* dynamically created and populated using the entries in the map.
*
*
* For parameterized types, if the target type is {@link List} or
* {@link Map}, the value is wrapped in an instance of the same type that
* automatically coerces its elements or values, respectively, to the
* appropriate type. Other parameterized types are not supported.
*
* For reference types, null
values are returned as is. For
* numeric or boolean primitives, they are converted to 0 or
* false
, respectively.
*
* @param
* The target type.
*
* @param value
* The value to coerce.
*
* @param type
* The target type.
*
* @return
* The coerced value.
*/
@SuppressWarnings("unchecked")
public static T coerce(Object value, Type type) {
if (type == null) {
throw new IllegalArgumentException();
}
if (type instanceof Class>) {
return (T)coerce(value, (Class>)type);
} else if (type instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType)type;
Type rawType = parameterizedType.getRawType();
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
if (rawType == List.class && (value == null || value instanceof List>)) {
return (T)coerceList((List>)value, actualTypeArguments[0]);
} else if (rawType == Map.class && (value == null || value instanceof Map, ?>)) {
return (T)coerceMap((Map, ?>)value, actualTypeArguments[1]);
} else {
throw new IllegalArgumentException();
}
} else {
throw new IllegalArgumentException();
}
}
private static Object coerce(Object value, Class> type) {
if (List.class.isAssignableFrom(type) || Map.class.isAssignableFrom(type)) {
throw new IllegalArgumentException();
}
if (type.isInstance(value)) {
return value;
} else if (type == Byte.TYPE || type == Byte.class) {
if (value == null) {
return (type == Byte.TYPE) ? Byte.valueOf((byte)0) : null;
} else if (value instanceof Number) {
return ((Number)value).byteValue();
} else {
return Byte.parseByte(value.toString());
}
} else if (type == Short.TYPE || type == Short.class) {
if (value == null) {
return (type == Short.TYPE) ? Short.valueOf((short)0) : null;
} else if (value instanceof Number) {
return ((Number)value).shortValue();
} else {
return Short.parseShort(value.toString());
}
} else if (type == Integer.TYPE || type == Integer.class) {
if (value == null) {
return (type == Integer.TYPE) ? Integer.valueOf(0) : null;
} else if (value instanceof Number) {
return ((Number)value).intValue();
} else {
return Integer.parseInt(value.toString());
}
} else if (type == Long.TYPE || type == Long.class) {
if (value == null) {
return (type == Long.TYPE) ? Long.valueOf(0) : null;
} else if (value instanceof Number) {
return ((Number)value).longValue();
} else {
return Long.parseLong(value.toString());
}
} else if (type == Float.TYPE || type == Float.class) {
if (value == null) {
return (type == Float.TYPE) ? Float.valueOf(0) : null;
} else if (value instanceof Number) {
return ((Number)value).floatValue();
} else {
return Float.parseFloat(value.toString());
}
} else if (type == Double.TYPE || type == Double.class) {
if (value == null) {
return (type == Double.TYPE) ? Double.valueOf(0) : null;
} else if (value instanceof Number) {
return ((Number)value).doubleValue();
} else {
return Double.parseDouble(value.toString());
}
} else if (type == Boolean.TYPE || type == Boolean.class) {
if (value == null) {
return (type == Boolean.TYPE) ? Boolean.FALSE : null;
} else if (value instanceof Byte
|| value instanceof Short
|| value instanceof Integer
|| value instanceof Long) {
return ((Number)value).longValue() != 0;
} else if (value instanceof Float
|| value instanceof Double) {
return ((Number)value).doubleValue() != 0.0;
} else {
return Boolean.parseBoolean(value.toString());
}
} else {
if (value == null) {
return null;
}
if (type == String.class) {
return value.toString();
} else if (type == Date.class) {
if (value instanceof Number) {
return new Date(((Number)value).longValue());
} else {
return new Date(Long.parseLong(value.toString()));
}
} else if (type == Instant.class) {
if (value instanceof Date) {
return ((Date)value).toInstant();
} else {
return Instant.parse(value.toString());
}
} else if (type == LocalDate.class) {
return LocalDate.parse(value.toString());
} else if (type == LocalTime.class) {
return LocalTime.parse(value.toString());
} else if (type == LocalDateTime.class) {
return LocalDateTime.parse(value.toString());
} else if (type == Duration.class) {
return Duration.parse(value.toString());
} else if (type == Period.class) {
return Period.parse(value.toString());
} else if (type == UUID.class) {
return UUID.fromString(value.toString());
} else if (type == URL.class) {
try {
return new URL(value.toString());
} catch (MalformedURLException exception) {
throw new IllegalArgumentException(exception);
}
} else if (type.isEnum()) {
String name = value.toString();
Field[] fields = type.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
if (!field.isEnumConstant()) {
continue;
}
Object constant;
try {
constant = field.get(null);
} catch (IllegalAccessException exception) {
throw new RuntimeException(exception);
}
if (name.equals(constant.toString())) {
return constant;
}
}
throw new IllegalArgumentException();
} else {
if (!(value instanceof Map, ?>)) {
throw new IllegalArgumentException();
}
Map, ?> map = (Map, ?>)value;
if (type.isInterface()) {
return type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class>[] {type}, new TypedInvocationHandler(map)));
} else {
Constructor> constructor;
try {
constructor = type.getConstructor();
} catch (NoSuchMethodException exception) {
throw new RuntimeException(exception);
}
Object bean;
try {
bean = constructor.newInstance();
} catch (InstantiationException | IllegalAccessException | InvocationTargetException exception) {
throw new RuntimeException(exception);
}
BeanAdapter beanAdapter = new BeanAdapter(bean);
for (Map.Entry, ?> entry : map.entrySet()) {
try {
beanAdapter.put(entry.getKey().toString(), entry.getValue());
} catch (UnsupportedOperationException exception) {
// No-op
}
}
return bean;
}
}
}
}
/**
* Coerces list elements to a given type.
*
* @param
* The target element type.
*
* @param list
* The source list.
*
* @param elementType
* The target element type.
*
* @return
* An list implementation that will coerce the list's elements to the
* requested type.
*/
public static List coerceList(List> list, Type elementType) {
if (list == null) {
return null;
}
if (elementType == null) {
throw new IllegalArgumentException();
}
return new AbstractList() {
@Override
public E get(int index) {
return coerce(list.get(index), elementType);
}
@Override
public int size() {
return list.size();
}
@Override
public Iterator iterator() {
return new Iterator() {
private Iterator> iterator = list.iterator();
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public E next() {
return coerce(iterator.next(), elementType);
}
};
}
};
}
/**
* Coerces map values to a given type.
*
* @param
* The key type.
*
* @param
* The target value type.
*
* @param map
* The source map.
*
* @param valueType
* The target value type.
*
* @return
* A map implementation that will coerce the map's values to the requested
* type.
*/
public static Map coerceMap(Map map, Type valueType) {
if (map == null) {
return null;
}
if (valueType == null) {
throw new IllegalArgumentException();
}
return new AbstractMap() {
@Override
public V get(Object key) {
return coerce(map.get(key), valueType);
}
@Override
public Set> entrySet() {
return new AbstractSet>() {
@Override
public int size() {
return map.size();
}
@Override
public Iterator> iterator() {
return new Iterator>() {
private Iterator extends Entry> iterator = map.entrySet().iterator();
@Override
public boolean hasNext() {
return iterator.hasNext();
}
@Override
public Entry next() {
return new Entry() {
private Entry entry = iterator.next();
@Override
public K getKey() {
return entry.getKey();
}
@Override
public V getValue() {
return coerce(entry.getValue(), valueType);
}
@Override
public V setValue(Object value) {
throw new UnsupportedOperationException();
}
};
}
};
}
};
}
};
}
/**
* Generates a parameterized type descriptor.
*
* @param rawType
* The raw type.
*
* @param actualTypeArguments
* The actual type arguments.
*
* @return
* A parameterized type that describes the given raw type and actual type
* arguments.
*/
public static ParameterizedType typeOf(Class> rawType, Type... actualTypeArguments) {
if (rawType == null) {
throw new IllegalArgumentException();
}
if (actualTypeArguments == null) {
throw new IllegalArgumentException();
}
if (rawType.getTypeParameters().length != actualTypeArguments.length) {
throw new IllegalArgumentException();
}
return new ParameterizedType() {
@Override
public Type[] getActualTypeArguments() {
return actualTypeArguments;
}
@Override
public Type getRawType() {
return rawType;
}
@Override
public Type getOwnerType() {
return null;
}
};
}
/**
* Returns the properties for a given type.
*
* @param type
* The bean type.
*
* @return
* The properties defined by the requested type.
*/
public static Map getProperties(Class> type) {
Map properties = new TreeMap<>();
Method[] methods = type.getMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (method.getDeclaringClass() == Object.class) {
continue;
}
String key = getKey(method);
if (key != null) {
Property property = properties.get(key);
if (property == null) {
property = new Property();
properties.put(key, property);
}
if (method.getParameterCount() == 0) {
property.accessor = method;
} else {
property.mutators.add(method);
}
}
}
return properties;
}
private static String getKey(Method method) {
if (method.isBridge()) {
return null;
}
String methodName = method.getName();
Class> returnType = method.getReturnType();
int parameterCount = method.getParameterCount();
String prefix;
if (methodName.startsWith(GET_PREFIX)
&& !(returnType == Void.TYPE || returnType == Void.class)
&& parameterCount == 0) {
prefix = GET_PREFIX;
} else if (methodName.startsWith(IS_PREFIX)
&& !(returnType == Void.TYPE || returnType == Void.class)
&& parameterCount == 0) {
prefix = IS_PREFIX;
} else if (methodName.startsWith(SET_PREFIX)
&& (returnType == Void.TYPE || returnType == Void.class)
&& parameterCount == 1) {
prefix = SET_PREFIX;
} else {
return null;
}
Key key = method.getAnnotation(Key.class);
if (key == null) {
int j = prefix.length();
int n = methodName.length();
if (j == n) {
return null;
}
char c = methodName.charAt(j++);
if (j == n || Character.isLowerCase(methodName.charAt(j))) {
c = Character.toLowerCase(c);
}
return c + methodName.substring(j);
} else {
return key.value();
}
}
}