| /* |
| * 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.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Iterator; |
| 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 List<Method> getters = new ArrayList<Method>(); |
| final List<String> getterNames = new ArrayList<String>(); |
| final List<PropertyType> propertyType = new ArrayList<PropertyType>(); |
| } |
| |
| private enum PropertyType { |
| VALUE, REFERENCE, COLLECTION, MAP; |
| } |
| |
| private static final Map<Class<?>, Data> cache = new WeakHashMap<Class<?>, 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 Data calculateData(Class<?> beanType) { |
| Data toReturn; |
| synchronized (cache) { |
| toReturn = cache.get(beanType); |
| if (toReturn == null) { |
| toReturn = new Data(); |
| for (Method method : beanType.getMethods()) { |
| if (BeanMethod.GET.matches(method)) { |
| toReturn.getters.add(method); |
| |
| String name; |
| PropertyName annotation = method.getAnnotation(PropertyName.class); |
| if (annotation != null) { |
| name = annotation.value(); |
| } else { |
| name = BeanMethod.GET.inferName(method); |
| } |
| toReturn.getterNames.add(name); |
| |
| Class<?> returnType = method.getReturnType(); |
| if (TypeUtils.isValueType(returnType)) { |
| toReturn.propertyType.add(PropertyType.VALUE); |
| } else if (Collection.class.isAssignableFrom(returnType)) { |
| toReturn.propertyType.add(PropertyType.COLLECTION); |
| } else if (Map.class.isAssignableFrom(returnType)) { |
| toReturn.propertyType.add(PropertyType.MAP); |
| } else { |
| toReturn.propertyType.add(PropertyType.REFERENCE); |
| } |
| } |
| } |
| cache.put(beanType, toReturn); |
| } |
| } |
| return toReturn; |
| } |
| |
| private final Class<T> beanType; |
| private final Configuration configuration; |
| private final Data data; |
| /** |
| * 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 |
| * |______ WeakMapping ___| |
| * </pre> |
| */ |
| 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.data = 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.data = 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 = (T) ProxyAutoBean.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) { |
| assert data.getters.size() == data.getterNames.size() |
| && data.getters.size() == data.propertyType.size(); |
| Iterator<Method> getterIt = data.getters.iterator(); |
| Iterator<String> nameIt = data.getterNames.iterator(); |
| Iterator<PropertyType> typeIt = data.propertyType.iterator(); |
| while (getterIt.hasNext()) { |
| Method getter = getterIt.next(); |
| String name = nameIt.next(); |
| PropertyType propertyType = typeIt.next(); |
| |
| // 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 = |
| isUsingSimplePeer() ? new BeanPropertyContext(this, getter) : new GetterPropertyContext( |
| this, getter); |
| |
| 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; |
| } |
| } |
| } |
| } |
| |
| Class<?> getBeanType() { |
| return beanType; |
| } |
| |
| private T createShim() { |
| T toReturn = ProxyAutoBean.makeProxy(beanType, new ShimHandler<T>(this, getWrapped())); |
| WeakMapping.set(toReturn, AutoBean.class.getName(), this); |
| return toReturn; |
| } |
| } |