Package com.google.gwt.uibinder.rebind

Source Code of com.google.gwt.uibinder.rebind.JClassTypeAdapter

/*
* Copyright 2009 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.uibinder.rebind;

import com.google.gwt.core.ext.typeinfo.HasAnnotations;
import com.google.gwt.core.ext.typeinfo.HasTypeParameters;
import com.google.gwt.core.ext.typeinfo.JAbstractMethod;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JConstructor;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameter;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
import com.google.gwt.core.ext.typeinfo.JType;

import static org.mockito.Mockito.any;
import static org.mockito.Mockito.when;

import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.GenericDeclaration;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Creates stub adapters for GWT reflection using EasyMock.
* This takes in a real java reflection class and returns a JClassType which
* forwards all its method calls to the equivalent java reflection methods.
* <p>
* Most reflections are done lazily (only when the method is actually called),
* specially those which potentially require mocking a new class / method /
* field / parameter / constructor / etc.
* <p>
* The support for the typeinfo API is still incomplete, and in fact will
* always be in some way since Java doesn't support all the reflections that
* GWT's typeinfo does.
* <p>
* TODO: With a bit of generalization, this class should make its way
* to core/src/com/google/gwt/junit
*
* To make it public we need to...
* <ol>
* <li>implement the missing parts and TODOs (e.g. generics)
* <li>add tests to it (even though it's a testing utility, it does need
* tests, specially for cases like inner and anonymous classes, generic
* parameters, etc.)
* <li>decide what to do with the parts of JType reflection that java doesn't
* have an equivalent for - e.g. parameter names. This may involve making a
* slightly more complex API to inject those values.
* </ol>
*/
public class JClassTypeAdapter {

  private final Map<Class<?>, JClassType> adaptedClasses =
    new HashMap<Class<?>, JClassType>();
  private final List<Object> allMocks = new ArrayList<Object>();

  /**
   * Creates a mock GWT class type for the given Java class.
   *
   * @param clazz the java class
   * @return the gwt class
   */
  public JClassType adaptJavaClass(final Class<?> clazz) {
    if (clazz.isPrimitive()) {
      throw new RuntimeException(
          "Only classes can be passed to adaptJavaClass");
    }

    // First try the cache (also avoids infinite recursion if a type references
    // itself).
    JClassType type = adaptedClasses.get(clazz);
    if (type != null) {
      return type;
    }

    // Create and put in the cache
    type = createMock(JClassType.class);
    final JClassType finalType = type;
    adaptedClasses.put(clazz, type);

    // Adds behaviour for annotations and generics
    addAnnotationBehaviour(clazz, type);

    // TODO(rdamazio): Add generics behaviour

    // Add behaviour for getting methods
    when(type.getMethods()).thenAnswer(new Answer<JMethod[]>() {
      @Override
      public JMethod[] answer(InvocationOnMock invocation) throws Throwable {
        Method[] realMethods = clazz.getDeclaredMethods();
        JMethod[] methods = new JMethod[realMethods.length];
        for (int i = 0; i < realMethods.length; i++) {
          methods[i] = adaptMethod(realMethods[i], finalType);
        }
        return methods;
      }
    });

    // Add behaviour for getting constructors
    when(type.getConstructors()).thenAnswer(new Answer<JConstructor[]>() {
      @Override
      public JConstructor[] answer(InvocationOnMock invocation) throws Throwable {
        Constructor<?>[] realConstructors = clazz.getDeclaredConstructors();
        JConstructor[] constructors = new JConstructor[realConstructors.length];
        for (int i = 0; i < realConstructors.length; i++) {
          constructors[i] = adaptConstructor(realConstructors[i], finalType);
        }
        return constructors;
      }
    });

    // Add behaviour for getting fields
    when(type.getFields()).thenAnswer(new Answer<JField[]>() {
      @Override
      public JField[] answer(InvocationOnMock invocation) throws Throwable {
        Field[] realFields = clazz.getDeclaredFields();
        JField[] fields = new JField[realFields.length];
        for (int i = 0; i < realFields.length; i++) {
          fields[i] = adaptField(realFields[i], finalType);
        }
        return fields;
      }
    });

    // Add behaviour for getting names
    when(type.getName()).thenReturn(clazz.getName());
    when(type.getQualifiedSourceName()).thenReturn(
        clazz.getCanonicalName());
    when(type.getSimpleSourceName()).thenReturn(clazz.getSimpleName());

    // Add modifier behaviour
    int modifiers = clazz.getModifiers();
    when(type.isAbstract()).thenReturn(Modifier.isAbstract(modifiers));
    when(type.isFinal()).thenReturn(Modifier.isFinal(modifiers));
    when(type.isPublic()).thenReturn(Modifier.isPublic(modifiers));
    when(type.isProtected()).thenReturn(Modifier.isProtected(modifiers));
    when(type.isPrivate()).thenReturn(Modifier.isPrivate(modifiers));

    // Add conversion behaviours
    when(type.isArray()).thenReturn(null);
    when(type.isEnum()).thenReturn(null);
    when(type.isPrimitive()).thenReturn(null);
    when(type.isClassOrInterface()).thenReturn(type);
    if (clazz.isInterface()) {
      when(type.isClass()).thenReturn(null);
      when(type.isInterface()).thenReturn(type);
    } else {
      when(type.isClass()).thenReturn(type);
      when(type.isInterface()).thenReturn(null);
    }
    when(type.getEnclosingType()).thenAnswer(new Answer<JClassType>() {
      @Override
      public JClassType answer(InvocationOnMock invocation) throws Throwable {
        Class<?> enclosingClass = clazz.getEnclosingClass();
        if (enclosingClass == null) {
          return null;
        }

        return adaptJavaClass(enclosingClass);
      }
    });
    when(type.getSuperclass()).thenAnswer(new Answer<JClassType>() {
      @Override
      public JClassType answer(InvocationOnMock invocation) throws Throwable {
        Class<?> superclass = clazz.getSuperclass();
        if (superclass == null) {
          return null;
        }

        return adaptJavaClass(superclass);
      }
    });

    // TODO(rdamazio): Mock out other methods as needed
    // TODO(rdamazio): Figure out what to do with reflections that GWT allows
    //                 but Java doesn't

    return type;
  }

  /**
   * Creates a mock GWT field for the given Java field.
   *
   * @param realField the java field
   * @param enclosingType the GWT enclosing type
   * @return the GWT field
   */
  public JField adaptField(final Field realField, JClassType enclosingType) {
    JField field = createMock(JField.class);

    addAnnotationBehaviour(realField, field);

    when(field.getType()).thenAnswer(new Answer<JType>() {
      @Override
      public JType answer(InvocationOnMock invocation) throws Throwable {
        return adaptType(realField.getType());
      }
    });

    when(field.getEnclosingType()).thenReturn(enclosingType);
    when(field.getName()).thenReturn(realField.getName());

    return field;
  }

  /**
   * Creates a mock GWT constructor for the given java constructor.
   *
   * @param realConstructor the java constructor
   * @param enclosingType the type to which the constructor belongs
   * @return the GWT constructor
   */
  private JConstructor adaptConstructor(final Constructor<?> realConstructor,
      JClassType enclosingType) {
    final JConstructor constructor = createMock(JConstructor.class);

    addCommonAbstractMethodBehaviour(realConstructor, constructor,
        enclosingType);
    addAnnotationBehaviour(realConstructor, constructor);

    // Parameters
    when(constructor.getParameters()).thenAnswer(
        new Answer<JParameter[]>() {
          @Override
          public JParameter[] answer(InvocationOnMock invocation) throws Throwable {
            return adaptParameters(realConstructor.getParameterTypes(),
                realConstructor.getParameterAnnotations(), constructor);
          }
        });

    // Thrown exceptions
    when(constructor.getThrows()).thenAnswer(
        new Answer<JClassType[]>() {
          @Override
          public JClassType[] answer(InvocationOnMock invocation) throws Throwable {
            Class<?>[] realThrows = realConstructor.getExceptionTypes();
            JClassType[] gwtThrows = new JClassType[realThrows.length];
            for (int i = 0; i < realThrows.length; i++) {
              gwtThrows[i] = (JClassType) adaptType(realThrows[i]);
            }
            return gwtThrows;
          }
        });

    return constructor;
  }

  /**
   * Creates a mock GWT method for the given java method.
   *
   * @param realMethod the java method
   * @param enclosingType the type to which the method belongs
   * @return the GWT method
   */
  private JMethod adaptMethod(final Method realMethod,
      JClassType enclosingType) {
    // TODO(rdamazio): ensure a single instance per method per class
    final JMethod method = createMock(JMethod.class);

    addCommonAbstractMethodBehaviour(realMethod, method, enclosingType);
    addAnnotationBehaviour(realMethod, method);
    addGenericsBehaviour(realMethod, method);

    when(method.isStatic()).thenReturn(
        Modifier.isStatic(realMethod.getModifiers()));

    // Return type
    when(method.getReturnType()).thenAnswer(new Answer<JType>() {
      @Override
      public JType answer(InvocationOnMock invocation) throws Throwable {
        return adaptType(realMethod.getReturnType());
      }
    });

    // Parameters
    when(method.getParameters()).thenAnswer(new Answer<JParameter[]>() {
      @Override
      public JParameter[] answer(InvocationOnMock invocation) throws Throwable {
        return adaptParameters(realMethod.getParameterTypes(),
            realMethod.getParameterAnnotations(), method);
      }
    });

    // Thrown exceptions
    when(method.getThrows()).thenAnswer(new Answer<JClassType[]>() {
      @Override
      public JClassType[] answer(InvocationOnMock invocation) throws Throwable {
        Class<?>[] realThrows = realMethod.getExceptionTypes();
        JClassType[] gwtThrows = new JClassType[realThrows.length];
        for (int i = 0; i < realThrows.length; i++) {
          gwtThrows[i] = (JClassType) adaptType(realThrows[i]);
        }
        return gwtThrows;
      }
    });

    return method;
  }

  /**
   * Creates an array of mock GWT parameters for the given array of java
   * parameters.
   *
   * @param parameterTypes the types of the parameters
   * @param parameterAnnotations the list of annotations for each parameter
   * @param method the method or constructor to which the parameters belong
   * @return an array of GWT parameters
   */
  @SuppressWarnings("unchecked")
  protected JParameter[] adaptParameters(Class<?>[] parameterTypes,
      Annotation[][] parameterAnnotations, JAbstractMethod method) {
    JParameter[] parameters = new JParameter[parameterTypes.length];
    for (int i = 0; i < parameterTypes.length; i++) {
      final Class<?> realParameterType = parameterTypes[i];
      JParameter parameter = createMock(JParameter.class);
      parameters[i] = parameter;

      // TODO(rdamazio): getName() has no plain java equivalent.
      //                 Perhaps compiling with -g:vars ?

      when(parameter.getEnclosingMethod()).thenReturn(method);
      when(parameter.getType()).thenAnswer(new Answer<JType>() {
        @Override
        public JType answer(InvocationOnMock invocation) throws Throwable {
          return adaptType(realParameterType);
        }
      });

      // Add annotation behaviour
      final Annotation[] annotations = parameterAnnotations[i];

      when(parameter.isAnnotationPresent(any(Class.class))).thenAnswer(
          new Answer<Boolean>() {

            @Override
            public Boolean answer(InvocationOnMock invocation) throws Throwable {
              Class<? extends Annotation> annotationClass =
                  (Class<? extends Annotation>)
                  invocation.getArguments()[0];
              for (Annotation annotation : annotations) {
                if (annotation.equals(annotationClass)) {
                  return true;
                }
              }
              return false;
            }
          });

      when(parameter.getAnnotation(any(Class.class))).thenAnswer(
          new Answer<Annotation>() {
            @Override
            public Annotation answer(InvocationOnMock invocation) throws Throwable {
              Class<? extends Annotation> annotationClass =
                  (Class<? extends Annotation>)
                  invocation.getArguments()[0];
              for (Annotation annotation : annotations) {
                if (annotation.equals(annotationClass)) {
                  return annotation;
                }
              }
              return null;
            }
          });
    }

    return parameters;
  }

  /**
   * Creates a GWT mock type for the given java type.
   * The type can be a class or a primitive type.
   *
   * @param type the java type
   * @return the GWT type
   */
  private JType adaptType(Class<?> type) {
    if (!type.isPrimitive()) {
      return adaptJavaClass(type);
    } else {
      return adaptPrimitiveType(type);
    }
  }

  /**
   * Returns the GWT primitive type for the given java primitive type.
   *
   * @param type the java primitive type
   * @return the GWT primitive equivalent
   */
  private JType adaptPrimitiveType(Class<?> type) {
    if (boolean.class.equals(type)) { return JPrimitiveType.BOOLEAN; }
    if (int.class.equals(type)) { return JPrimitiveType.INT; }
    if (char.class.equals(type)) { return JPrimitiveType.CHAR; }
    if (byte.class.equals(type)) { return JPrimitiveType.BYTE; }
    if (long.class.equals(type)) { return JPrimitiveType.LONG; }
    if (short.class.equals(type)) { return JPrimitiveType.SHORT; }
    if (float.class.equals(type)) { return JPrimitiveType.FLOAT; }
    if (double.class.equals(type)) { return JPrimitiveType.DOUBLE; }
    if (void.class.equals(type)) { return JPrimitiveType.VOID; }

    throw new IllegalArgumentException(
        "Invalid primitive type: " + type.getName());
  }

  /**
   * Adds expectations common to all method types (methods and constructors).
   *
   * @param realMember the java method
   * @param member the mock GWT method
   * @param enclosingType the type to which the method belongs
   */
  private void addCommonAbstractMethodBehaviour(Member realMember,
      JAbstractMethod member, JClassType enclosingType) {
    // Attributes
    int modifiers = realMember.getModifiers();
    when(member.isPublic()).thenReturn(Modifier.isPublic(modifiers));
    when(member.isProtected()).thenReturn(Modifier.isProtected(modifiers));
    when(member.isPrivate()).thenReturn(Modifier.isPrivate(modifiers));
    when(member.getName()).thenReturn(realMember.getName());
    when(member.getEnclosingType()).thenReturn(enclosingType);
  }

  /**
   * Adds expectations for getting annotations from elements (methods, classes,
   * parameters, etc.).
   *
   * @param realElement the java element which contains annotations
   * @param element the mock GWT element which contains annotations
   */
  @SuppressWarnings("unchecked")
  private void addAnnotationBehaviour(final AnnotatedElement realElement,
      final HasAnnotations element) {
    when(element.isAnnotationPresent(any(Class.class))).thenAnswer(
        new Answer<Boolean>() {
          @Override
          public Boolean answer(InvocationOnMock invocation) throws Throwable {
            Class<? extends Annotation> annotationClass =
                (Class<? extends Annotation>) invocation.getArguments()[0];
            return realElement.isAnnotationPresent(annotationClass);
          }
        });

    when(element.getAnnotation(any(Class.class))).thenAnswer(
        new Answer<Annotation>() {
          @Override
          public Annotation answer(InvocationOnMock invocation) throws Throwable {
            Class<? extends Annotation> annotationClass =
                (Class<? extends Annotation>) invocation.getArguments()[0];
            return realElement.getAnnotation(annotationClass);
          }
        });
  }

  /**
   * Adds expectations for getting generics types.
   *
   * @param realGeneric the java generic declaration
   * @param generic the mock GWT generic declaration
   */
  private void addGenericsBehaviour(final GenericDeclaration realGeneric,
      final HasTypeParameters generic) {
    // TODO(rdamazio): Implement when necessary
  }

  /**
   * Creates a mock of the given class and adds it to the {@link #allMocks}
   * member list.
   *
   * @param <T> the type of the mock
   * @param clazz the class of the mock
   * @return the mock
   */
  private <T> T createMock(Class<T> clazz) {
    T mock = Mockito.mock(clazz);
    allMocks.add(mock);
    return mock;
  }
}
TOP

Related Classes of com.google.gwt.uibinder.rebind.JClassTypeAdapter

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.