Package com.googlecode.gwt.test.internal

Source Code of com.googlecode.gwt.test.internal.AutomaticPatcher

package com.googlecode.gwt.test.internal;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.bytecode.Descriptor;
import javassist.bytecode.annotation.AnnotationImpl;
import javassist.bytecode.annotation.StringMemberValue;

import com.googlecode.gwt.test.exceptions.GwtTestPatchException;
import com.googlecode.gwt.test.patchers.InitMethod;
import com.googlecode.gwt.test.patchers.PatchMethod;
import com.googlecode.gwt.test.patchers.PatchMethod.ParamType;
import com.googlecode.gwt.test.utils.GwtReflectionUtils;

/**
* <p>
* The default class for implementing {@link Patcher} methods. It relies on convenient annotations
* {@link InitMethod} which marks a method to be a bytecode initialization callback and {link
* PatchMethod} which marks a method to be a substitution for another one following a simple
* convention.
* </p>
* <p>
* <strong>For internal use only.</strong>
* </p>
*
* @author Bertrand Paquet
* @author Gael Lazzari
*
*/
class AutomaticPatcher implements Patcher {

   private final CtMethod initMethod;
   private final Set<CtMethod> patchMethods;
   private final Set<CtMethod> processedMethods;

   AutomaticPatcher(Set<CtClass> patchClasses) {
      for (CtClass patchClass : patchClasses) {
         patchClass.setModifiers(Modifier.PUBLIC);
      }

      this.initMethod = getInitMethod(patchClasses);
      this.patchMethods = getPatchMethods(patchClasses);
      this.processedMethods = new HashSet<CtMethod>();
   }

   public void finalizeClass(CtClass c) throws Exception {
      // FIXME : re-activate this code when gwt-bootstrap will work with bootstrap 2.1.1

      // for (CtMethod m : patchMethods) {
      // if (!processedMethods.contains(m)) {
      // throw new GwtTestPatchException("@" + PatchMethod.class.getSimpleName()
      // + " not used : " + m.getLongName());
      // }
      // }
   }

   public String getNewBody(CtMethod m) throws Exception {
      CtMethod patchMethod = findPatchMethod(m);
      if (patchMethod == null) {
         return null;
      }

      processedMethods.add(patchMethod);

      // construction of the redirection code
      StringBuilder buffer = new StringBuilder();
      buffer.append("{");
      buffer.append("return ");
      buffer.append(patchMethod.getDeclaringClass().getName() + "." + patchMethod.getName());
      buffer.append("(");
      boolean append = false;

      if (!Modifier.isStatic(m.getModifiers())) {
         buffer.append("this");
         append = true;
      }
      for (int i = 0; i < m.getParameterTypes().length; i++) {
         if (append) {
            buffer.append(", ");
         }
         int j = i + 1;
         buffer.append("$" + j);
         append = true;
      }
      buffer.append(");");
      buffer.append("}");

      return buffer.toString();
   }

   public void initClass(CtClass c) throws Exception {
      if (initMethod == null) {
         return;
      }

      Class<?> clazz = GwtReflectionUtils.getClass(initMethod.getDeclaringClass().getName());
      Method compiledMethod = clazz.getDeclaredMethod(initMethod.getName(), CtClass.class);
      // TODO : this should not be mandatory since AutomaticPatcher set every
      // initMethod to public when loaded by GwtClassLoader
      GwtReflectionUtils.makeAccessible(compiledMethod);
      try {
         compiledMethod.invoke(null, c);
      } catch (InvocationTargetException e) {
         if (Exception.class.isInstance(e)) {
            throw (Exception) e.getCause();
         } else {
            throw e;
         }
      }
   }

   private String[] extractPatchMethodParameterTypes(CtMethod patchMethod, boolean isStatic)
            throws NotFoundException, ClassNotFoundException {

      CtClass[] patchMethodParamTypes = patchMethod.getParameterTypes();
      int length = isStatic ? patchMethodParamTypes.length : patchMethodParamTypes.length - 1;
      String[] result = new String[length];

      Object[][] parameterAnntations = patchMethod.getParameterAnnotations();

      for (int i = isStatic ? 0 : 1; i < patchMethodParamTypes.length; i++) {
         Object[] annotations = parameterAnntations[i];

         String paramType = null;
         if (annotations.length > 0) {
            paramType = tryExtractFromParamTypeAnnotation(annotations);
         }

         if (paramType == null) {
            // default
            paramType = patchMethodParamTypes[i].getName();
         }

         result[isStatic ? i : i - 1] = paramType;

      }

      return result;
   }

   private CtMethod findPatchMethod(CtMethod m) throws Exception {
      for (CtMethod patchMethod : patchMethods) {
         PatchMethod annotation = (PatchMethod) patchMethod.getAnnotation(PatchMethod.class);
         String methodName = (annotation.value().length() > 0) ? annotation.value()
                  : patchMethod.getName();

         if (m.getName().equals(methodName) && hasCompatibleSignature(m, patchMethod)) {
            return patchMethod;
         }
      }

      return null;
   }

   private CtMethod getInitMethod(Set<CtClass> patchClasses) {
      List<CtMethod> initMethods = new ArrayList<CtMethod>();

      for (CtClass patchClass : patchClasses) {
         for (CtMethod ctMethod : patchClass.getDeclaredMethods()) {
            if (ctMethod.hasAnnotation(InitMethod.class)) {
               if (!Modifier.isStatic(ctMethod.getModifiers())) {
                  throw new GwtTestPatchException("@" + InitMethod.class.getSimpleName()
                           + " has to be static : '" + ctMethod.getLongName() + "'");
               }
               try {
                  if (ctMethod.getParameterTypes().length != 1
                           || ctMethod.getParameterTypes()[0] != GwtClassPool.getCtClass(CtClass.class)) {
                     throw new GwtTestPatchException("@" + InitMethod.class.getName()
                              + " method must have one and only one parameter of type '"
                              + CtClass.class.getName() + "'");
                  }
               } catch (NotFoundException e) {
                  // should never happen
                  throw new GwtTestPatchException(e);
               }
               initMethods.add(ctMethod);
            }
         }
      }

      CtMethod initMethod = getMethodToUse(initMethods, InitMethod.class);
      if (initMethod != null) {
         initMethod.setModifiers(Modifier.PUBLIC + Modifier.STATIC);
      }
      return initMethod;

   }

   @SuppressWarnings("unchecked")
   private <T extends Annotation> T getMethodAnnotation(CtMethod ctMethod, Class<T> annotation) {
      try {
         return (T) ctMethod.getAnnotation(annotation);
      } catch (ClassNotFoundException e) {
         // should never happen
         throw new GwtTestPatchException(e);
      }
   }

   private <T extends Annotation> CtMethod getMethodToUse(List<CtMethod> methods,
            Class<T> annotationClass) {
      switch (methods.size()) {
         case 0:
            return null;
         case 1:
            return methods.get(0);
         default:
            return getOverrideMethod(methods, annotationClass);
      }
   }

   private <T extends Annotation> CtMethod getOverrideMethod(List<CtMethod> methods,
            Class<T> annotationClass) {
      CtMethod overrideInitMethod = null;

      for (CtMethod method : methods) {
         T annotation = getMethodAnnotation(method, annotationClass);
         if (isOverride(annotation)) {
            if (overrideInitMethod != null) {
               throw new GwtTestPatchException("There are more than one @"
                        + annotationClass.getSimpleName()
                        + " with 'override=true' for the same target : '" + method.getLongName()
                        + "' and '" + overrideInitMethod.getLongName() + "'");
            } else {
               overrideInitMethod = method;
            }
         }
      }

      if (overrideInitMethod == null) {
         StringBuilder sb = new StringBuilder();
         sb.append(methods.size()).append(" @");
         sb.append(annotationClass.getSimpleName());
         sb.append(" methods detected for the same target, but no one is set to override the other. You must use 'override=true' on the one which should be applied : ");

         for (CtMethod method : methods) {
            sb.append("'").append(method.getLongName()).append("'");
            sb.append(" or ");
         }
         throw new GwtTestPatchException(sb.substring(0, sb.length() - 4));
      }

      return overrideInitMethod;
   }

   private Set<CtMethod> getPatchMethods(Set<CtClass> patchClasses) {
      Set<CtMethod> result = new HashSet<CtMethod>();

      // add all @PatchMethod found in a temporary map
      Map<String, List<CtMethod>> temp = new HashMap<String, List<CtMethod>>();
      for (CtClass patchClass : patchClasses) {
         for (CtMethod ctMethod : patchClass.getDeclaredMethods()) {
            if (ctMethod.hasAnnotation(PatchMethod.class)) {
               if (!Modifier.isStatic(ctMethod.getModifiers())) {
                  throw new GwtTestPatchException("@" + PatchMethod.class.getName()
                           + " has to be static : '" + ctMethod.getLongName() + "'");
               }
               String nameAndSignature = ctMethod.getName()
                        + Descriptor.toString(ctMethod.getSignature());
               List<CtMethod> correspondingMethods = temp.get(nameAndSignature);

               if (correspondingMethods == null) {
                  correspondingMethods = new ArrayList<CtMethod>();
                  temp.put(nameAndSignature, correspondingMethods);
               }

               correspondingMethods.add(ctMethod);
            }
         }
      }

      // for each @PatchMethod with the same signature, filter to get one with
      // override=true
      for (Map.Entry<String, List<CtMethod>> entry : temp.entrySet()) {
         CtMethod methodToUse = getMethodToUse(entry.getValue(), PatchMethod.class);
         methodToUse.setModifiers(Modifier.PUBLIC + Modifier.STATIC);
         result.add(methodToUse);
      }

      return result;

   }

   private boolean hasCompatibleSignature(CtMethod methodFound, CtMethod patchMethod)
            throws Exception {

      boolean isStatic = Modifier.isStatic(methodFound.getModifiers());

      if (!isStatic
               && (patchMethod.getParameterTypes().length == 0 || !methodFound.getDeclaringClass().subtypeOf(
                        patchMethod.getParameterTypes()[0]))) {

         return false;
      }

      CtClass[] orgMethodParamTypes = methodFound.getParameterTypes();
      String[] patchMethodParamTypes = extractPatchMethodParameterTypes(patchMethod, isStatic);

      if (orgMethodParamTypes.length != patchMethodParamTypes.length) {
         return false;
      }

      for (int i = 0; i < orgMethodParamTypes.length; i++) {
         if (!orgMethodParamTypes[i].getName().equals(patchMethodParamTypes[i])) {
            return false;
         }
      }

      return true;
   }

   private boolean isOverride(Annotation annotation) {
      if (InitMethod.class.isInstance(annotation)) {
         return ((InitMethod) annotation).override();
      } else {
         return ((PatchMethod) annotation).override();
      }
   }

   private String tryExtractFromParamTypeAnnotation(Object[] annotations) {
      for (Object o : annotations) {

         AnnotationImpl annotation = (AnnotationImpl) Proxy.getInvocationHandler(o);
         if (annotation.getTypeName().equals(ParamType.class.getName())) {
            String value = ((StringMemberValue) annotation.getAnnotation().getMemberValue("value")).getValue();

            return value.equals("") ? null : value;
         }
      }

      return null;
   }
}
TOP

Related Classes of com.googlecode.gwt.test.internal.AutomaticPatcher

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.