// -*- mode: java; c-basic-offset: 2; -*- // Copyright 2009-2011 Google, All Rights reserved // Copyright 2011-2012 MIT, All rights reserved // Released under the Apache License, Version 2.0 // http://www.apache.org/licenses/LICENSE-2.0 package com.google.appinventor.components.runtime.util; import com.google.appinventor.components.annotations.SimpleProperty; import com.google.appinventor.components.annotations.SimplePropertyCopier; import com.google.appinventor.components.runtime.Component; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * Utilities for copying properties between different instances of the same component. * * @author markf@google.com (Mark Friedman) */ public class PropertyUtil { /** * Copy properties from one component to another of the same class. * @param source the source component * @param target the target component * @return the target component (for convenience in chaining) * @throws Throwable */ public static Component copyComponentProperties(Component source, Component target) throws Throwable { if (!source.getClass().equals(target.getClass())) { throw new IllegalArgumentException("Source and target classes must be identical"); } final Class componentClass = source.getClass(); final Method[] componentMethods = componentClass.getMethods(); for (Method componentMethod : componentMethods) { // A SimpleProperty which takes a single argument is a property setter. if (componentMethod.isAnnotationPresent(SimpleProperty.class) && componentMethod.getParameterTypes().length == 1) { final Method propertySetterMethod = componentMethod; try { final String propertyName = propertySetterMethod.getName(); // Look for a property copier method. final Method propertyCopierMethod = getPropertyCopierMethod("Copy" + propertyName, componentClass); if (propertyCopierMethod != null) { propertyCopierMethod.invoke(target, source); continue; // Don't return here because we still need to copy more properties. } // It's fine if there's no copy method. We'll look for a getter. // The getter will have the same name as the setter final Method propertyGetterMethod = componentClass.getMethod(propertyName); final Class propertySetterParameterType = propertySetterMethod.getParameterTypes()[0]; // The getter also needs to be a SimpleProperty and its return type needs to be // compatible with the setter's single argument type if (propertyGetterMethod.isAnnotationPresent(SimpleProperty.class) && propertySetterParameterType.isAssignableFrom(propertyGetterMethod.getReturnType())) { final Object propertyValue = propertyGetterMethod.invoke(source); propertySetterMethod.invoke(target, propertyValue); } } catch (NoSuchMethodException e) { // It's fine if there's no getter method. We just won't invoke the setter. continue; } catch (InvocationTargetException e2) { // This re-throws any Exceptions generated by the property getters or setters themselves. throw e2.getCause(); } } } return target; } private static Method getPropertyCopierMethod(String copierMethodName, Class componentClass) { // If the copier method is declared in a superclass of componentClass, the parameter // would be the superclass also. // For example, componentClass might be Button. But the CopyWidth method is declared in // AndroidViewComponent and the paremeter is also AndroidViewComponent. // So, we may need to look up the superclass chain to do { try { Method propertyCopierMethod = componentClass.getMethod(copierMethodName, componentClass); if (propertyCopierMethod.isAnnotationPresent(SimplePropertyCopier.class)) { return propertyCopierMethod; } } catch (NoSuchMethodException e) { // It's fine if there's no copier method. We'll try the superclass. } componentClass = componentClass.getSuperclass(); } while (componentClass != null); // It's fine if we didn't find a copier method. Just return null; return null; } }