Package org.eclipse.jdt.internal.compiler.apt.model

Source Code of org.eclipse.jdt.internal.compiler.apt.model.AnnotationMirrorImpl

/*******************************************************************************
* Copyright (c) 2005, 2009 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.compiler.apt.model;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.MirroredTypeException;
import javax.lang.model.type.MirroredTypesException;
import javax.lang.model.type.TypeMirror;

import org.eclipse.jdt.internal.compiler.apt.dispatch.BaseProcessingEnvImpl;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.AnnotationBinding;
import org.eclipse.jdt.internal.compiler.lookup.ArrayBinding;
import org.eclipse.jdt.internal.compiler.lookup.ElementValuePair;
import org.eclipse.jdt.internal.compiler.lookup.FieldBinding;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;

public class AnnotationMirrorImpl implements AnnotationMirror, InvocationHandler {
 
  public final BaseProcessingEnvImpl _env;
  public final AnnotationBinding _binding;
 
  /* package */ AnnotationMirrorImpl(BaseProcessingEnvImpl env, AnnotationBinding binding) {
    _env = env;
    _binding = binding;
  }
 
  @Override
  public boolean equals(Object obj) {
    if (obj instanceof AnnotationMirrorImpl) {
      if (this._binding == null) {
        return ((AnnotationMirrorImpl) obj)._binding == null;
      }
      return equals(this._binding, ((AnnotationMirrorImpl) obj)._binding);
    }
    return false;
  }

  private static boolean equals(AnnotationBinding annotationBinding, AnnotationBinding annotationBinding2) {
    if (annotationBinding.getAnnotationType() != annotationBinding2.getAnnotationType()) return false;
    final ElementValuePair[] elementValuePairs = annotationBinding.getElementValuePairs();
    final ElementValuePair[] elementValuePairs2 = annotationBinding2.getElementValuePairs();
    final int length = elementValuePairs.length;
    if (length != elementValuePairs2.length) return false;
    loop: for (int i = 0; i < length; i++) {
      ElementValuePair pair = elementValuePairs[i];
      // loop on the given pair to make sure one will match
      for (int j = 0; j < length; j++) {
        ElementValuePair pair2 = elementValuePairs2[j];
        if (pair.binding == pair2.binding) {
          if (pair.value == null) {
            if (pair2.value == null) {
              continue loop;
            }
            return false;
          } else {
            if (pair2.value == null
                || !pair2.value.equals(pair.value)) {
              return false;
            }
          }
          continue loop;
        }
      }
      return false;
    }
    return true;
  }

  public DeclaredType getAnnotationType() {
    if (this._binding == null) {
      return _env.getFactory().getErrorType();
    }
    ReferenceBinding annoType = _binding.getAnnotationType();
    return _env.getFactory().newAnnotationType(annoType);
  }
 
  /**
   * @return all the members of this annotation mirror that have explicit values.
   * Default values are not included.
   */
  public Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues() {
    if (this._binding == null) {
      return Collections.emptyMap();
    }
    ElementValuePair[] pairs = _binding.getElementValuePairs();
    Map<ExecutableElement, AnnotationValue> valueMap =
      new LinkedHashMap<ExecutableElement, AnnotationValue>(pairs.length);
    for (ElementValuePair pair : pairs) {
      MethodBinding method = pair.getMethodBinding();
      if (method == null) {
        // ideally we should be able to create a fake ExecutableElementImpl
        continue;
      }
      ExecutableElement e = new ExecutableElementImpl(_env, method);
      AnnotationValue v = new AnnotationMemberValue(_env, pair.getValue(), method);
      valueMap.put(e, v);
    }
    return Collections.unmodifiableMap(valueMap);
  }
 
  /**
   * {@see Elements#getElementValuesWithDefaults()}
   * @return all the members of this annotation mirror that have explicit or default
   * values.
   */
  public Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValuesWithDefaults() {
    if (this._binding == null) {
      return Collections.emptyMap();
    }
    ElementValuePair[] pairs = _binding.getElementValuePairs();
    ReferenceBinding annoType = _binding.getAnnotationType();
    Map<ExecutableElement, AnnotationValue> valueMap =
      new LinkedHashMap<ExecutableElement, AnnotationValue>();
    for (MethodBinding method : annoType.methods()) {
      // if binding is in ElementValuePair list, then get value from there
      boolean foundExplicitValue = false;
      for (int i = 0; i < pairs.length; ++i) {
        MethodBinding explicitBinding = pairs[i].getMethodBinding();
        if (method == explicitBinding) {
          ExecutableElement e = new ExecutableElementImpl(_env, explicitBinding);
          AnnotationValue v = new AnnotationMemberValue(_env, pairs[i].getValue(), explicitBinding);
          valueMap.put(e, v);
          foundExplicitValue = true;
          break;
        }
      }
      // else get default value if one exists
      if (!foundExplicitValue) {
        Object defaultVal = method.getDefaultValue();
        if (null != defaultVal) {
          ExecutableElement e = new ExecutableElementImpl(_env, method);
          AnnotationValue v = new AnnotationMemberValue(_env, defaultVal, method);
          valueMap.put(e, v);
        }
      }
    }
    return Collections.unmodifiableMap(valueMap);
  }
 
  public int hashCode() {
    if (this._binding == null) return this._env.hashCode();
    return this._binding.hashCode();
  }

  /*
   * Used by getAnnotation(), which returns a reflective proxy of the annotation class.  When processors then
   * invoke methods such as value() on the annotation proxy, this method is called.
   * <p>
   * A challenge here is that the processor was not necessarily compiled against the same annotation
   * definition that the compiler is looking at right now, not to mention that the annotation itself
   * may be defective in source.  So the actual type of the value may be quite different than the
   * type expected by the caller, which will result in a ClassCastException, which is ugly for the
   * processor to try to catch.  So we try to catch and correct this type mismatch where possible.
   * <p>
   * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
  {
    if (this._binding == null) return null;
        final String methodName = method.getName();
        if ( args == null || args.length == 0 ) {
            if( methodName.equals("hashCode") ) { //$NON-NLS-1$
                return new Integer( hashCode() );
            }
            else if( methodName.equals("toString") ) { //$NON-NLS-1$
                return toString();
            }
            else if( methodName.equals("annotationType")) { //$NON-NLS-1$
              return proxy.getClass().getInterfaces()[0];
            }
        }
        else if ( args.length == 1 && methodName.equals("equals") ) { //$NON-NLS-1$
            return new Boolean( equals( args[0] ) );
        }
       
        // If it's not one of the above methods, it must be an annotation member, so it cannot take any arguments
        if ( args != null && args.length != 0 ) {
            throw new NoSuchMethodException("method " + method.getName() + formatArgs(args) + " does not exist on annotation " + toString()); //$NON-NLS-1$ //$NON-NLS-2$
        }
        final MethodBinding methodBinding = getMethodBinding(methodName);
        if ( methodBinding == null ) {
            throw new NoSuchMethodException("method " + method.getName() + "() does not exist on annotation" + toString()); //$NON-NLS-1$ //$NON-NLS-2$
        }

        Object actualValue = null;
        boolean foundMethod = false;
        ElementValuePair[] pairs = _binding.getElementValuePairs();
    for (ElementValuePair pair : pairs) {
      if (methodName.equals(new String(pair.getName()))) {
        actualValue = pair.getValue();
        foundMethod = true;
        break;
      }
    }
    if (!foundMethod) {
      // couldn't find explicit value; see if there's a default
      actualValue = methodBinding.getDefaultValue();
    }
    Class<?> expectedType = method.getReturnType();
    TypeBinding actualType = methodBinding.returnType;
        return getReflectionValue(actualValue, actualType, expectedType);
  }

  /*
   * (non-Javadoc)
   * Sun implementation shows the values.  We avoid that here,
   * because getting the values is not idempotent.
   */
  @Override
  public String toString() {
    if (this._binding == null) {
      return "@any()"; //$NON-NLS-1$
    }
    return "@" + _binding.getAnnotationType().debugName(); //$NON-NLS-1$
  }
 
  /**
   * Used for constructing exception message text.
   * @return a string like "(a, b, c)".
   */
    private String formatArgs(final Object[] args)
    {
        // estimate that each class name (plus the separators) is 10 characters long plus 2 for "()".
        final StringBuilder builder = new StringBuilder(args.length * 8 + 2 );
        builder.append('(');
        for( int i=0; i<args.length; i++ )
        {
            if( i > 0 )
              builder.append(", "); //$NON-NLS-1$
            builder.append(args[i].getClass().getName());
        }
        builder.append(')');
        return builder.toString();
    }
   
  /**
   * Find a particular annotation member by name.
   * @return a compiler method binding, or null if no member was found.
   */
  private MethodBinding getMethodBinding(String name) {
    ReferenceBinding annoType = _binding.getAnnotationType();
    MethodBinding[] methods = annoType.getMethods(name.toCharArray());
    for (MethodBinding method : methods) {
      // annotation members have no parameters
      if (method.parameters.length == 0) {
        return method;
      }
    }
    return null;
  }

  /**
   * Convert an annotation member value from JDT into Reflection, and from whatever its actual type
   * is into whatever type the reflective invoker of a method is expecting.
   * <p>
   * Only certain types are permitted as member values.  Specifically, a member must be a constant,
   * and must be either a primitive type, String, Class, an enum constant, an annotation, or an
   * array of any of those.  Multidimensional arrays are not permitted.
   *
   * @param annoValue the value as represented by {@link ElementValuePair#getValue()}
   * @param actualType the return type of the corresponding {@link MethodBinding}
   * @param expectedType the type that the reflective method invoker is expecting
   * @return an object of the expected type representing the annotation member value,
   * or an appropriate dummy value (such as null) if no value is available
   */
  private Object getReflectionValue(Object actualValue, TypeBinding actualType, Class<?> expectedType)
  {
    if (null == expectedType) {
      // With no expected type, we can't even guess at a conversion
      return null;
    }
    if (null == actualValue) {
      // Return a type-appropriate equivalent of null
      return Factory.getMatchingDummyValue(expectedType);
    }
    if (expectedType.isArray()) {
      if (Class.class.equals(expectedType.getComponentType())) {
        // package Class[]-valued return as a MirroredTypesException
        if (actualType.isArrayType() && actualValue instanceof Object[] &&
            ((ArrayBinding)actualType).leafComponentType.erasure().id == TypeIds.T_JavaLangClass) {
          Object[] bindings = (Object[])actualValue;
          List<TypeMirror> mirrors = new ArrayList<TypeMirror>(bindings.length);
          for (int i = 0; i < bindings.length; ++i) {
            if (bindings[i] instanceof TypeBinding) {
              mirrors.add(_env.getFactory().newTypeMirror((TypeBinding)bindings[i]));
            }
          }
          throw new MirroredTypesException(mirrors);
        }
        // TODO: actual value is not a TypeBinding[].  Should we return a TypeMirror[] around an ErrorType?
        return null;
      }
      // Handle arrays of types other than Class, e.g., int[], MyEnum[], ...
      return convertJDTArrayToReflectionArray(actualValue, actualType, expectedType);
    }
    else if (Class.class.equals(expectedType)) {
      // package the Class-valued return as a MirroredTypeException
      if (actualValue instanceof TypeBinding) {
        TypeMirror mirror = _env.getFactory().newTypeMirror((TypeBinding)actualValue);
        throw new MirroredTypeException(mirror);
      }
      else {
        // TODO: actual value is not a TypeBinding.  Should we return a TypeMirror around an ErrorType?
        return null;
      }
    }
    else {
      // Handle unitary values of type other than Class, e.g., int, MyEnum, ...
      return convertJDTValueToReflectionType(actualValue, actualType, expectedType);
    }
  }

  /**
   * Convert an array of JDT types as obtained from ElementValuePair.getValue()
   * (e.g., an Object[] containing IntConstant elements) to the type expected by
   * a reflective method invocation (e.g., int[]).
   * <p>
   * This does not handle arrays of Class, but it does handle primitives, enum constants,
   * and types such as String.
   * @param jdtValue the actual value returned by ElementValuePair.getValue() or MethodBinding.getDefault()
   * @param jdtType the return type of the annotation method binding
   * @param expectedType the type that the invoker of the method is expecting; must be an array type
   * @return an Object which is, e.g., an int[]; or null, if an array cannot be created.
   */
  private Object convertJDTArrayToReflectionArray(Object jdtValue, TypeBinding jdtType, Class<?> expectedType)
  {
    assert null != expectedType && expectedType.isArray();
    if (!jdtType.isArrayType() || !(jdtValue instanceof Object[])) {
      // TODO: wrap solo element into one-length array
      return null;
    }
    TypeBinding jdtLeafType = jdtType.leafComponentType();
    Object[] jdtArray = (Object[])jdtValue;
    Class<?> expectedLeafType = expectedType.getComponentType();
        final int length = jdtArray.length;
        final Object returnArray = Array.newInstance(expectedLeafType, length);
        for (int i = 0; i < length; ++i) {
          Object jdtElementValue = jdtArray[i];
        if (expectedLeafType.isPrimitive() || String.class.equals(expectedLeafType)) {
          if (jdtElementValue instanceof Constant) {
            if (boolean.class.equals(expectedLeafType)) {
              Array.setBoolean(returnArray, i, ((Constant)jdtElementValue).booleanValue());
            }
            else if (byte.class.equals(expectedLeafType)) {
              Array.setByte(returnArray, i, ((Constant)jdtElementValue).byteValue());
            }
            else if (char.class.equals(expectedLeafType)) {
              Array.setChar(returnArray, i, ((Constant)jdtElementValue).charValue());
            }
            else if (double.class.equals(expectedLeafType)) {
              Array.setDouble(returnArray, i, ((Constant)jdtElementValue).doubleValue());
            }
            else if (float.class.equals(expectedLeafType)) {
              Array.setFloat(returnArray, i, ((Constant)jdtElementValue).floatValue());
            }
            else if (int.class.equals(expectedLeafType)) {
              Array.setInt(returnArray, i, ((Constant)jdtElementValue).intValue());
            }
            else if (long.class.equals(expectedLeafType)) {
              Array.setLong(returnArray, i, ((Constant)jdtElementValue).longValue());
            }
            else if (short.class.equals(expectedLeafType)) {
              Array.setShort(returnArray, i, ((Constant)jdtElementValue).shortValue());
            }
            else if (String.class.equals(expectedLeafType)) {
              Array.set(returnArray, i, ((Constant)jdtElementValue).stringValue());
            }
          }
          else {
            // Primitive or string is expected, but our actual value cannot be coerced into one.
            // TODO: if the actual value is an array of primitives, should we unpack the first one?
            Factory.setArrayMatchingDummyValue(returnArray, i, expectedLeafType);
          }
        }
        else if (expectedLeafType.isEnum()) {
          Object returnVal = null;
              if (jdtLeafType != null && jdtLeafType.isEnum() && jdtElementValue instanceof FieldBinding) {
                FieldBinding binding = (FieldBinding)jdtElementValue;
                try {
                  Field returnedField = null;
                  returnedField = expectedLeafType.getField( new String(binding.name) );
                  if (null != returnedField) {
                    returnVal = returnedField.get(null);
                  }
                }
                catch (NoSuchFieldException nsfe) {
                  // return null
                }
                catch (IllegalAccessException iae) {
                  // return null
                }
              }
              Array.set(returnArray, i, returnVal);
        }
        else if (expectedLeafType.isAnnotation()) {
          // member value is expected to be an annotation type.  Wrap it in an Annotation proxy.
          Object returnVal = null;
          if (jdtLeafType.isAnnotationType() && jdtElementValue instanceof AnnotationBinding) {
            AnnotationMirrorImpl annoMirror =
              (AnnotationMirrorImpl)_env.getFactory().newAnnotationMirror((AnnotationBinding)jdtElementValue);
            returnVal = Proxy.newProxyInstance(expectedLeafType.getClassLoader(),
                new Class[]{ expectedLeafType }, annoMirror );
          }
              Array.set(returnArray, i, returnVal);
        }
        else {
          Array.set(returnArray, i, null);
        }
        }
    return returnArray;
  }

  /**
   * Convert a JDT annotation value as obtained from ElementValuePair.getValue()
   * (e.g., IntConstant, FieldBinding, etc.) to the type expected by a reflective
   * method invocation (e.g., int, an enum constant, etc.).
   * @return a value of type {@code expectedType}, or a dummy value of that type if
   * the actual value cannot be converted.
   */
  private Object convertJDTValueToReflectionType(Object jdtValue, TypeBinding actualType, Class<?> expectedType) {
    if (expectedType.isPrimitive() || String.class.equals(expectedType)) {
      if (jdtValue instanceof Constant) {
        if (boolean.class.equals(expectedType)) {
          return ((Constant)jdtValue).booleanValue();
        }
        else if (byte.class.equals(expectedType)) {
          return ((Constant)jdtValue).byteValue();
        }
        else if (char.class.equals(expectedType)) {
          return ((Constant)jdtValue).charValue();
        }
        else if (double.class.equals(expectedType)) {
          return ((Constant)jdtValue).doubleValue();
        }
        else if (float.class.equals(expectedType)) {
          return ((Constant)jdtValue).floatValue();
        }
        else if (int.class.equals(expectedType)) {
          return ((Constant)jdtValue).intValue();
        }
        else if (long.class.equals(expectedType)) {
          return ((Constant)jdtValue).longValue();
        }
        else if (short.class.equals(expectedType)) {
          return ((Constant)jdtValue).shortValue();
        }
        else if (String.class.equals(expectedType)) {
          return ((Constant)jdtValue).stringValue();
        }
      }
      // Primitive or string is expected, but our actual value cannot be coerced into one.
      // TODO: if the actual value is an array of primitives, should we unpack the first one?
      return Factory.getMatchingDummyValue(expectedType);
    }
    else if (expectedType.isEnum()) {
      Object returnVal = null;
          if (actualType != null && actualType.isEnum() && jdtValue instanceof FieldBinding) {
           
            FieldBinding binding = (FieldBinding)jdtValue;
            try {
              Field returnedField = null;
              returnedField = expectedType.getField( new String(binding.name) );
              if (null != returnedField) {
                returnVal = returnedField.get(null);
              }
            }
            catch (NoSuchFieldException nsfe) {
              // return null
            }
            catch (IllegalAccessException iae) {
              // return null
            }
          }
          return null == returnVal ? Factory.getMatchingDummyValue(expectedType) : returnVal;
    }
    else if (expectedType.isAnnotation()) {
      // member value is expected to be an annotation type.  Wrap it in an Annotation proxy.
      if (actualType.isAnnotationType() && jdtValue instanceof AnnotationBinding) {
        AnnotationMirrorImpl annoMirror =
          (AnnotationMirrorImpl)_env.getFactory().newAnnotationMirror((AnnotationBinding)jdtValue);
        return Proxy.newProxyInstance(expectedType.getClassLoader(),
            new Class[]{ expectedType }, annoMirror );
      }
      else {
        // No way to cast a non-annotation value to an annotation type; return null to caller
        return null;
      }
    }
    else {
      return Factory.getMatchingDummyValue(expectedType);
    }
  }

}
TOP

Related Classes of org.eclipse.jdt.internal.compiler.apt.model.AnnotationMirrorImpl

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.