/* * 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.AutoBean.PropertyName; import java.lang.reflect.Method; import java.lang.reflect.Modifier; /** * Breakout of method types that an AutoBean shim interface can implement. The * order of the values of the enum is important. * * @see com.google.web.bindery.autobean.gwt.rebind.model.JBeanMethod */ public enum BeanMethod { /** * Methods defined in Object. */ OBJECT { @Override public String inferName(Method method) { throw new UnsupportedOperationException(); } @Override Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) throws Throwable { if (CALL.matches(handler, method)) { return CALL.invoke(handler, method, args); } return method.invoke(handler, args); } @Override boolean matches(SimpleBeanHandler<?> handler, Method method) { return method.getDeclaringClass().equals(Object.class); } }, /** * Getters. */ GET { @Override public String inferName(Method method) { String name = method.getName(); if (name.startsWith(IS_PREFIX) && !method.isAnnotationPresent(PropertyName.class)) { Class<?> returnType = method.getReturnType(); if (Boolean.TYPE.equals(returnType) || Boolean.class.equals(returnType)) { return decapitalize(name.substring(2)); } } return super.inferName(method); } @Override Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) { String propertyName = inferName(method); Object toReturn = handler.getBean().getOrReify(propertyName); if (toReturn == null && method.getReturnType().isPrimitive()) { toReturn = TypeUtils.getDefaultPrimitiveValue(method.getReturnType()); } return toReturn; } @Override boolean matches(SimpleBeanHandler<?> handler, Method method) { Class<?> returnType = method.getReturnType(); if (method.getParameterTypes().length != 0 || Void.TYPE.equals(returnType)) { return false; } String name = method.getName(); if (Boolean.TYPE.equals(returnType) || Boolean.class.equals(returnType)) { if (name.startsWith(IS_PREFIX) && name.length() > 2 || name.startsWith(HAS_PREFIX) && name.length() > 3) { return true; } } return name.startsWith(GET_PREFIX) && name.length() > 3; } }, /** * Setters. */ SET { @Override Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) { handler.getBean().setProperty(inferName(method), args[0]); return null; } @Override boolean matches(SimpleBeanHandler<?> handler, Method method) { String name = method.getName(); return name.startsWith(SET_PREFIX) && name.length() > 3 && method.getParameterTypes().length == 1 && method.getReturnType().equals(Void.TYPE); } }, /** * A setter that returns a type assignable from the interface in which the * method is declared to support chained, builder-pattern setters. For * example, {@code foo.setBar(1).setBaz(42)}. */ SET_BUILDER { @Override Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) { ProxyAutoBean<?> bean = handler.getBean(); bean.setProperty(inferName(method), args[0]); return bean.as(); } @Override boolean matches(SimpleBeanHandler<?> handler, Method method) { String name = method.getName(); return name.startsWith(SET_PREFIX) && name.length() > 3 && method.getParameterTypes().length == 1 && method.getReturnType().isAssignableFrom(method.getDeclaringClass()); } }, /** * Domain methods. */ CALL { @Override public String inferName(Method method) { throw new UnsupportedOperationException(); } @Override Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) throws Throwable { if (args == null) { args = EMPTY_OBJECT; } Method found = findMethod(handler, method); if (found != null) { Object[] realArgs = new Object[args.length + 1]; realArgs[0] = handler.getBean(); System.arraycopy(args, 0, realArgs, 1, args.length); return found.invoke(null, realArgs); } throw new RuntimeException("Could not find category implementation of " + method.toGenericString()); } @Override boolean matches(SimpleBeanHandler<?> handler, Method method) { return handler.getBean().isWrapper() || !handler.getBean().getConfiguration().getCategories().isEmpty() && findMethod(handler, method) != null; } }; public static final String GET_PREFIX = "get"; public static final String HAS_PREFIX = "has"; public static final String IS_PREFIX = "is"; public static final String SET_PREFIX = "set"; private static final Object[] EMPTY_OBJECT = new Object[0]; static Method findMethod(SimpleBeanHandler<?> handler, Method method) { Class<?>[] declaredParams = method.getParameterTypes(); Class<?>[] searchParams = new Class<?>[declaredParams.length + 1]; searchParams[0] = AutoBean.class; System.arraycopy(declaredParams, 0, searchParams, 1, declaredParams.length); Class<?> autoBeanType = handler.getBean().getType(); for (Class<?> clazz : handler.getBean().getConfiguration().getCategories()) { try { Method found = clazz.getMethod(method.getName(), searchParams); if (!Modifier.isStatic(found.getModifiers())) { continue; } // Check the AutoBean parameterization of the 0th argument Class<?> foundAutoBean = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(AutoBean.class, found .getGenericParameterTypes()[0])); if (!foundAutoBean.isAssignableFrom(autoBeanType)) { continue; } return found; } catch (NoSuchMethodException expected) { } catch (IllegalArgumentException e) { throw new RuntimeException(e); } } return null; } /** * Private equivalent of Introspector.decapitalize(String) since * java.beans.Introspector is not available in Android 2.2. */ private static String decapitalize(String name) { if (name == null) { return null; } int length = name.length(); if (length == 0 || (length > 1 && Character.isUpperCase(name.charAt(1)))) { return name; } StringBuilder sb = new StringBuilder(length); sb.append(Character.toLowerCase(name.charAt(0))); sb.append(name.substring(1)); return sb.toString(); } public String inferName(Method method) { PropertyName prop = method.getAnnotation(PropertyName.class); if (prop != null) { return prop.value(); } return decapitalize(method.getName().substring(3)); } /** * Convenience method, not valid for {@link BeanMethod#CALL}. */ public boolean matches(Method method) { return matches(null, method); } /** * Invoke the method. */ abstract Object invoke(SimpleBeanHandler<?> handler, Method method, Object[] args) throws Throwable; /** * Determine if the method maches the given type. */ abstract boolean matches(SimpleBeanHandler<?> handler, Method method); }