/* * 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.web.bindery.autobean.shared.AutoBean; import com.google.web.bindery.autobean.shared.AutoBeanUtils; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * Implements an AutoBean's shim interface that intercepts calls to the backing * object. * * @param <T> the interface type of the AutoBean */ class ShimHandler<T> implements InvocationHandler { private final ProxyAutoBean<T> bean; private final Method interceptor; public ShimHandler(ProxyAutoBean<T> bean, T toWrap) { this.bean = bean; Method maybe = null; for (Class<?> clazz : bean.getConfiguration().getCategories()) { try { maybe = clazz.getMethod("__intercept", AutoBean.class, Object.class); break; } catch (SecurityException expected) { } catch (NoSuchMethodException expected) { } } interceptor = maybe; } @Override public boolean equals(Object couldBeShim) { if (couldBeShim == null) { return false; } // Handles the foo.equals(foo) case if (Proxy.isProxyClass(couldBeShim.getClass()) && this == Proxy.getInvocationHandler(couldBeShim)) { return true; } return bean.getWrapped().equals(couldBeShim); } @Override public int hashCode() { return bean.getWrapped().hashCode(); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { method.setAccessible(true); Object toReturn; String name = method.getName(); method.setAccessible(true); try { if (BeanMethod.OBJECT.matches(method)) { return method.invoke(this, args); } else if (BeanMethod.GET.matches(method)) { toReturn = method.invoke(bean.getWrapped(), args); toReturn = bean.get(name, toReturn); } else if (BeanMethod.SET.matches(method) || BeanMethod.SET_BUILDER.matches(method)) { toReturn = method.invoke(bean.getWrapped(), args); bean.set(name, args[0]); } else { // XXX How should freezing and calls work together? toReturn = method.invoke(bean.getWrapped(), args); bean.call(name, toReturn, args); } Class<?> intf = method.getReturnType(); if (!Object.class.equals(intf)) { // XXX Need to deal with resolving generic T return types toReturn = maybeWrap(intf, toReturn); } if (interceptor != null) { toReturn = interceptor.invoke(null, bean, toReturn); } } catch (InvocationTargetException e) { throw e.getCause(); } return toReturn; } @Override public String toString() { return bean.getWrapped().toString(); } private Object maybeWrap(Class<?> intf, Object toReturn) { if (toReturn == null) { return null; } AutoBean<?> returnBean = AutoBeanUtils.getAutoBean(toReturn); if (returnBean != null) { return returnBean.as(); } if (TypeUtils.isValueType(intf) || TypeUtils.isValueType(toReturn.getClass()) || bean.getConfiguration().getNoWrap().contains(intf)) { return toReturn; } if (toReturn.getClass().isArray()) { /* * We can't reliably wrap arrays, but the only time we typically see an * array is with toArray() call on a collection, since arrays aren't * supported property types. */ return toReturn; } ProxyAutoBean<Object> newBean = new ProxyAutoBean<Object>(bean.getFactory(), intf, bean.getConfiguration(), toReturn); return newBean.as(); } }