| /* |
| * 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.gwt.autobean.server.impl; |
| |
| import com.google.gwt.autobean.shared.AutoBean; |
| import com.google.gwt.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.gwt.autobean.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); |
| } |