blob: fd9816e931d94b62be39b463d94235e2fc236082 [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* 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.google.web.bindery.autobean.vm.impl;
import com.google.gwt.core.client.impl.WeakMapping;
import com.google.web.bindery.autobean.shared.AutoBean;
import com.google.web.bindery.autobean.shared.AutoBeanFactory;
import com.google.web.bindery.autobean.shared.AutoBeanUtils;
import com.google.web.bindery.autobean.shared.AutoBeanVisitor;
import com.google.web.bindery.autobean.shared.impl.AbstractAutoBean;
import com.google.web.bindery.autobean.vm.Configuration;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
/**
* An implementation of an AutoBean that uses reflection.
*
* @param <T> the type of interface being wrapped
*/
public class ProxyAutoBean<T> extends AbstractAutoBean<T> {
private static class Data {
final Class<?> elementType;
final Type genericType;
final Method getter;
final Class<?> keyType;
final PropertyType propertyType;
Method setter;
final Class<?> type;
final Class<?> valueType;
Data(Method getter, Type genericType, Class<?> type, PropertyType propertyType) {
this.getter = getter;
this.genericType = genericType;
this.type = type;
this.propertyType = propertyType;
if (propertyType == PropertyType.COLLECTION) {
elementType =
TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(Collection.class,
genericType, type));
keyType = valueType = null;
} else if (propertyType == PropertyType.MAP) {
elementType = null;
Type[] types = TypeUtils.getParameterization(Map.class, genericType, type);
keyType = TypeUtils.ensureBaseType(types[0]);
valueType = TypeUtils.ensureBaseType(types[1]);
} else {
elementType = keyType = valueType = null;
}
}
}
private enum PropertyType {
VALUE, REFERENCE, COLLECTION, MAP;
}
private static final Map<Class<?>, Map<String, Data>> cache =
new WeakHashMap<Class<?>, Map<String, Data>>();
/**
* Utility method to crete a new {@link Proxy} instance.
*
* @param <T> the interface type to be implemented by the Proxy
* @param intf the Class representing the interface type
* @param handler the implementation of the interface
* @param extraInterfaces additional interface types the Proxy should
* implement
* @return a Proxy instance
*/
public static <T> T makeProxy(Class<T> intf, InvocationHandler handler,
Class<?>... extraInterfaces) {
Class<?>[] intfs;
if (extraInterfaces == null) {
intfs = new Class<?>[] {intf};
} else {
intfs = new Class<?>[extraInterfaces.length + 1];
intfs[0] = intf;
System.arraycopy(extraInterfaces, 0, intfs, 1, extraInterfaces.length);
}
return intf.cast(Proxy.newProxyInstance(intf.getClassLoader(), intfs, handler));
}
private static Map<String, Data> calculateData(Class<?> beanType) {
Map<String, Data> toReturn;
synchronized (cache) {
toReturn = cache.get(beanType);
if (toReturn == null) {
Map<String, Data> getters = new HashMap<String, Data>();
List<Method> setters = new ArrayList<Method>();
for (Method method : beanType.getMethods()) {
if (BeanMethod.GET.matches(method)) {
// match methods on their name for now, to find the most specific
// override
String name = method.getName();
Type genericReturnType = TypeUtils.resolveGenerics(beanType, method.getGenericReturnType());
Class<?> returnType = TypeUtils.ensureBaseType(genericReturnType);
Data data = getters.get(name);
if (data == null || data.type.isAssignableFrom(returnType)) {
// no getter seen yet for the property, or a less specific one
PropertyType propertyType;
if (TypeUtils.isValueType(returnType)) {
propertyType = PropertyType.VALUE;
} else if (Collection.class.isAssignableFrom(returnType)) {
propertyType = PropertyType.COLLECTION;
} else if (Map.class.isAssignableFrom(returnType)) {
propertyType = PropertyType.MAP;
} else {
propertyType = PropertyType.REFERENCE;
}
data = new Data(method, genericReturnType, returnType, propertyType);
getters.put(name, data);
}
} else if (BeanMethod.SET.matches(method) || BeanMethod.SET_BUILDER.matches(method)) {
setters.add(method);
}
}
toReturn = new HashMap<String, Data>(getters.size());
// Now take @PropertyName into account
for (Map.Entry<String, Data> entry : getters.entrySet()) {
Data data = entry.getValue();
toReturn.put(BeanMethod.GET.inferName(data.getter), data);
}
// Associate setters to getters
for (Method setter : setters) {
String name = BeanMethod.SET.inferName(setter);
Data data = toReturn.get(name);
if (data != null && data.setter == null
&& data.getter.getReturnType().isAssignableFrom(setter.getParameterTypes()[0])) {
data.setter = setter;
}
}
cache.put(beanType, toReturn);
}
}
return toReturn;
}
private final Class<T> beanType;
private final Configuration configuration;
private final Map<String, Data> propertyData;
/**
* Because the shim and the ProxyAutoBean are related through WeakMapping, we
* need to ensure that the ProxyAutoBean doesn't artificially extend the
* lifetime of the shim. If there are no external references to the shim, it's
* ok if it's deallocated, since it has no interesting state.
*
* <pre>
* _________________ ______________
* | ProxyAutoBean | | Shim |
* | | <----------+-bean |
* | shim-+---X------> | |
* |_______________| |____________|
* ^ ^ ^
* X X |
* |__value__WeakMapping__key__| |
* ^ |
* | |
* GC Roots -> Owner____|
* </pre>
* <p>
* In the case of a wrapped object (for example, an ArrayList), the weak
* reference from WeakMapping to the ProxyAutoBean may cause the AutoBean to
* be prematurely collected if neither the bean nor the shim are referenced
* elsewhere. The alternative is a massive memory leak.
*/
private WeakReference<T> shim;
// These constructors mirror the generated constructors.
@SuppressWarnings("unchecked")
public ProxyAutoBean(AutoBeanFactory factory, Class<?> beanType, Configuration configuration) {
super(factory);
this.beanType = (Class<T>) beanType;
this.configuration = configuration;
this.propertyData = calculateData(beanType);
}
@SuppressWarnings("unchecked")
public ProxyAutoBean(AutoBeanFactory factory, Class<?> beanType, Configuration configuration,
T toWrap) {
super(toWrap, factory);
this.beanType = (Class<T>) beanType;
this.configuration = configuration;
this.propertyData = calculateData(beanType);
}
@Override
public T as() {
T toReturn = shim == null ? null : shim.get();
if (toReturn == null) {
toReturn = createShim();
shim = new WeakReference<T>(toReturn);
}
return toReturn;
}
public Configuration getConfiguration() {
return configuration;
}
public Class<T> getType() {
return beanType;
}
/**
* Allow access by {@link ShimHandler}.
*/
@Override
protected void call(String method, Object returned, Object... parameters) {
super.call(method, returned, parameters);
}
/**
* Allow access by {@link ShimHandler}.
*/
@Override
protected void checkFrozen() {
super.checkFrozen();
}
/**
* Allow access by {@link ShimHandler}.
*/
@Override
protected void checkWrapped() {
super.checkWrapped();
}
/**
* Not used in this implementation. Instead, the simple implementation is
* created lazily in {@link #getWrapped()}.
*/
@Override
protected T createSimplePeer() {
return null;
}
/**
* Allow access by {@link ShimHandler}.
*/
@Override
protected <V> V get(String method, V toReturn) {
return super.get(method, toReturn);
}
/**
* Allow access by BeanMethod.
*/
@Override
protected <V> V getOrReify(String propertyName) {
return super.<V> getOrReify(propertyName);
}
/**
* Allow access by {@link ShimHandler}.
*/
@Override
protected T getWrapped() {
if (wrapped == null && isUsingSimplePeer()) {
wrapped = ProxyAutoBean.<T> makeProxy(beanType, new SimpleBeanHandler<T>(this));
}
return super.getWrapped();
}
/**
* Allow access by {@link ShimHandler}.
*/
@Override
protected void set(String method, Object value) {
super.set(method, value);
}
@Override
protected void setProperty(String propertyName, Object value) {
super.setProperty(propertyName, value);
}
// TODO: Port to model-based when class-based TypeOracle is available.
@Override
protected void traverseProperties(AutoBeanVisitor visitor, OneShotContext ctx) {
for (Map.Entry<String, Data> entry : propertyData.entrySet()) {
String name = entry.getKey();
Data data = entry.getValue();
Method getter = data.getter;
PropertyType propertyType = data.propertyType;
// Use the shim to handle automatic wrapping
Object value;
try {
getter.setAccessible(true);
value = getter.invoke(as());
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
// Create the context used for the property visitation
MethodPropertyContext x;
if (isUsingSimplePeer()) {
x =
new BeanPropertyContext(this, name, data.genericType, data.type, data.elementType,
data.keyType, data.valueType);
} else {
x =
new GetterPropertyContext(this, getter, data.genericType, data.type, data.elementType,
data.keyType, data.valueType);
}
switch (propertyType) {
case VALUE: {
if (visitor.visitValueProperty(name, value, x)) {
}
visitor.endVisitValueProperty(name, value, x);
break;
}
case COLLECTION: {
// Workaround for generics bug in mac javac 1.6.0_22
@SuppressWarnings("rawtypes")
AutoBean temp = AutoBeanUtils.getAutoBean((Collection) value);
@SuppressWarnings("unchecked")
AutoBean<Collection<?>> bean = (AutoBean<Collection<?>>) temp;
if (visitor.visitCollectionProperty(name, bean, x)) {
if (value != null) {
((ProxyAutoBean<?>) bean).traverse(visitor, ctx);
}
}
visitor.endVisitCollectionProperty(name, bean, x);
break;
}
case MAP: {
// Workaround for generics bug in mac javac 1.6.0_22
@SuppressWarnings("rawtypes")
AutoBean temp = AutoBeanUtils.getAutoBean((Map) value);
@SuppressWarnings("unchecked")
AutoBean<Map<?, ?>> bean = (AutoBean<Map<?, ?>>) temp;
if (visitor.visitMapProperty(name, bean, x)) {
if (value != null) {
((ProxyAutoBean<?>) bean).traverse(visitor, ctx);
}
}
visitor.endVisitMapProperty(name, bean, x);
break;
}
case REFERENCE: {
ProxyAutoBean<?> bean = (ProxyAutoBean<?>) AutoBeanUtils.getAutoBean(value);
if (visitor.visitReferenceProperty(name, bean, x)) {
if (value != null) {
bean.traverse(visitor, ctx);
}
}
visitor.endVisitReferenceProperty(name, bean, x);
break;
}
}
}
}
private T createShim() {
T toReturn = ProxyAutoBean.makeProxy(beanType, new ShimHandler<T>(this, getWrapped()));
WeakMapping.setWeak(toReturn, AutoBean.class.getName(), this);
return toReturn;
}
}