Package edu.cmu.cs.crystal.annotations

Source Code of edu.cmu.cs.crystal.annotations.AnnotationDatabase

/**
* Copyright (c) 2006, 2007, 2008 Marwan Abi-Antoun, Jonathan Aldrich, Nels E. Beckman, Kevin
* Bierhoff, David Dickey, Ciera Jaspan, Thomas LaToza, Gabriel Zenarosa, and others.
*
* This file is part of Crystal.
*
* Crystal is free software: you can redistribute it and/or modify it under the terms of the GNU
* Lesser General Public License as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* Crystal is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with Crystal. If
* not, see <http://www.gnu.org/licenses/>.
*/
package edu.cmu.cs.crystal.annotations;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMemberValuePair;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;

import edu.cmu.cs.crystal.internal.CrystalRuntimeException;
import edu.cmu.cs.crystal.util.Pair;

/**
* This class is a database for annotations. It can store annotations of methods we have analyzed or
* of ones we have not. Either way, it does not store any AST information, so it returns Crystal
* objects that represent an annotation. Those who want to use this database must register the type
* of annotations to scan for, @see{ICrystalAnnotation}.
*
* A ICrystalAnnotation provides all the information that one needs about the annotations. An
* AnnotationSummary is particularly useful for finding all information relating to the method,
* including parameters.
*
* And just in case you were wondering...we can not use regular reflection objects here (Class,
* Annotation, etc.) as the files we are analyzing aren't on the classpath of the system we are
* running.
*
* The annotation database currently allows clients to request annotations if they provide an ITypeBinding.
* However, this means that Crystal needs to have parsed and analyzed the code in question to get an ITypeBinding
* in the first place. Instead, we would like to retrieve annotations from unanalyzed code (for example, a library).
* This can currently be done with @link{#getAnnosForType(IType)}, and the plan is to eventually allow methods and
* fields based upon the Java Model instead of the Java ASTNodes.
*
* @author ciera
* @author Nels Beckman
*
*/
public class AnnotationDatabase {

  private static final Logger log = Logger.getLogger(AnnotationDatabase.class.getName());

  /**
   * {@link MultiAnnotation}'s fully qualified name or {@code null}
   * if it's not in the classpath (means edu.cmu.cs.planno plugin not loaded).
   * Notice that edu.cmu.cs.planno is an optional dependency so it may be missing.
   */
  private static final String MULTI_ANNOTATION_CLASSNAME;
  static {
    String className = null;
    try {
      className = MultiAnnotation.class.getName();
    } catch (Throwable t) {
      // Expect NoClassDefFoundError if MultiAnnotation is missing
      // catch-all just in case so we can keep going no matter what
      log.log(Level.INFO, "@MultiAnnotation not available, install edu.cmu.cs.planno plugin if you want to use multi-annotations", t);
    }
    MULTI_ANNOTATION_CLASSNAME = className;
  }

  private Map<String, Class<? extends ICrystalAnnotation>> qualNames;
  private Map<String, Class<? extends ICrystalAnnotation>> metaQualNames;

  private Map<String, AnnotationSummary> methods;
  private Map<String, List<ICrystalAnnotation>> classes;
  private Map<String, List<ICrystalAnnotation>> fields;

  public AnnotationDatabase() {
    qualNames = new HashMap<String, Class<? extends ICrystalAnnotation>>();
    metaQualNames = new HashMap<String, Class<? extends ICrystalAnnotation>>();
    methods = new HashMap<String, AnnotationSummary>();
    classes = new HashMap<String, List<ICrystalAnnotation>>();
    fields = new HashMap<String, List<ICrystalAnnotation>>();

  }

  public void register(String fullyQualifiedName,
      Class<? extends ICrystalAnnotation> crystalAnnotationClass, boolean isMeta) {
    Class<? extends ICrystalAnnotation> annoClass =
        isMeta ? metaQualNames.get(fullyQualifiedName) : qualNames.get(fullyQualifiedName);

    if (crystalAnnotationClass != null && annoClass != null
        && !(crystalAnnotationClass.isAssignableFrom(annoClass))) {
      throw new CrystalRuntimeException("Can not register " + fullyQualifiedName + " for "
          + crystalAnnotationClass.getCanonicalName() + ", the class "
          + annoClass.getCanonicalName() + " is already registered.");
    }
    if (isMeta)
      metaQualNames.put(fullyQualifiedName, crystalAnnotationClass);
    else
      qualNames.put(fullyQualifiedName, crystalAnnotationClass);
  }

  /***
   * Given a method binding, returns a summary that represents annotation info for that method
   * declaration.
   */
  public AnnotationSummary getSummaryForMethod(IMethodBinding binding) {
    while (binding != binding.getMethodDeclaration())
      binding = binding.getMethodDeclaration();
    String name = binding.getKey();

    AnnotationSummary result = methods.get(name);
    if (result == null) {
      result = createMethodSummary(binding);
      methods.put(name, result);
    }
    return result;
  }

  private AnnotationSummary createMethodSummary(IMethodBinding binding) {
    int paramCount = binding.getParameterTypes().length;
    String[] paramNames = new String[paramCount];
    for (int i = 0; i < paramCount; i++) {
      paramNames[i] = "arg" + i;
    }
    AnnotationSummary result = new AnnotationSummary(paramNames);
    result.addAllReturn(createAnnotations(binding.getAnnotations()));
    try {
      for (int i = 0; i < paramCount; i++) {
        result.addAllParameter(createAnnotations(binding.getParameterAnnotations(i)), i);
      }
    }
    catch (NullPointerException e) {
      if (log.isLoggable(Level.WARNING))
        log.log(Level.WARNING, "Bug in JDT (Eclipse 3.4M5) triggered in " + binding
            + ".  Not all annotations on parameters might be available.", e);
    }

    return result;
  }

  /**
   * This method will return the list of annotations associated with the
   * given type. It's very similar in functionality to
   * {@link #getAnnosForType(ITypeBinding)} except that it works on the
   * Java model element, so it does not require the type to have been parsed
   * and analyzed by Crystal.
   * @throws JavaModelException
   */
  public List<ICrystalAnnotation> getAnnosForType(IType type) throws JavaModelException {
    String name = type.getKey();

    List<ICrystalAnnotation> result = classes.get(name);
    if (result == null) {
      result = createAnnotations(type.getAnnotations(), type);
      classes.put(name, result);
    }
    return result;
  }
 
  /**
   * This method will return the list of annotations associated with the
   * given type. To work, this binding had to be parsed by Crystal. If you do not have
   * a type that was parsed by Crystal, use {@link #getAnnosForType(IType)}.
   */
  public List<ICrystalAnnotation> getAnnosForType(ITypeBinding type) {
    while (type != type.getTypeDeclaration())
      type = type.getTypeDeclaration();
    if (type.isPrimitive())
      return Collections.emptyList();
    if (type.isArray()) {
      log.warning("Annotations for array type requested: " + type.getName());
    }

    String name = type.getKey();

    List<ICrystalAnnotation> result = classes.get(name);
    if (result == null) {
      result = createAnnotations(type.getAnnotations());
      classes.put(name, result);
    }
    return result;
  }

  /**
   * This method will return the list of annotations associated with the
   * given variable.
   */
  public List<ICrystalAnnotation> getAnnosForVariable(IVariableBinding binding) {
    while (binding != binding.getVariableDeclaration())
      binding = binding.getVariableDeclaration();
    String name = binding.getKey();

    List<ICrystalAnnotation> result = fields.get(name);
    if (result == null) {
      result = createAnnotations(binding.getAnnotations());
      fields.put(name, result);
    }
    return result;
  }

  protected List<ICrystalAnnotation> createAnnotations(IAnnotationBinding[] bindings) {
    List<ICrystalAnnotation> result = new ArrayList<ICrystalAnnotation>(bindings.length);
    for (IAnnotationBinding anno : bindings) {
      result.addAll(createAnnotations(anno));
    }
    return Collections.unmodifiableList(result);
  }

  /**
   * See {@link #createAnnotations(IAnnotationBinding[])}.
   * @throws JavaModelException
   */
  protected List<ICrystalAnnotation> createAnnotations(IAnnotation[] annotations, IType relative_type) throws JavaModelException {
    List<ICrystalAnnotation> result = new ArrayList<ICrystalAnnotation>(annotations.length);
    for (IAnnotation anno : annotations) {
      result.addAll(createAnnotations(anno, relative_type));
    }
    return Collections.unmodifiableList(result);
  }
 
  /**
   * See {@link #createAnnotations(IAnnotationBinding)}.
   * @throws JavaModelException
   */
  protected List<ICrystalAnnotation> createAnnotations(IAnnotation anno, IType relative_type) throws JavaModelException {
    if (isMulti(anno, relative_type)) {
      for (IMemberValuePair pair : anno.getMemberValuePairs()) {
        Object value;
        if ("value".equals(pair.getMemberName()))
          value = pair.getValue();
        else if ("annos".equals(pair.getMemberName()))
          value = pair.getValue();
        else {
          log.warning("Ignore extra attribute in multi-annotation " + anno.getElementName()
              + ": " + pair.toString());
          continue;
        }
        if (value instanceof Object[]) {
          Object[] array = (Object[]) value;
          List<ICrystalAnnotation> result =
              new ArrayList<ICrystalAnnotation>(array.length);
          for (Object o : array) {
            result.add(createAnnotation((IAnnotation) o, relative_type));
          }
          return Collections.unmodifiableList(result);
        }
        else {
          // Eclipse doesn't desugar single-element arrays with omitted braces as arrays
          // https://bugs.eclipse.org/bugs/show_bug.cgi?id=223225
          return Collections.singletonList(createAnnotation((IAnnotationBinding) value));
        }
      }
      log.warning("Couldn't find annotation array in: " + anno);
    }
    return Collections.singletonList(createAnnotation(anno, relative_type));
  }
 
  protected List<ICrystalAnnotation> createAnnotations(IAnnotationBinding binding) {
    if (isMulti(binding)) {
      for (IMemberValuePairBinding pair : binding.getAllMemberValuePairs()) {
        Object value;
        if ("value".equals(pair.getName()))
          value = pair.getValue();
        else if ("annos".equals(pair.getName()))
          value = pair.getValue();
        else {
          log.warning("Ignore extra attribute in multi-annotation " + binding.getName()
              + ": " + pair.toString());
          continue;
        }
        if (value instanceof Object[]) {
          Object[] array = (Object[]) value;
          List<ICrystalAnnotation> result =
              new ArrayList<ICrystalAnnotation>(array.length);
          for (Object o : array) {
            result.add(createAnnotation((IAnnotationBinding) o));
          }
          return Collections.unmodifiableList(result);
        }
        else {
          // Eclipse doesn't desugar single-element arrays with omitted braces as arrays
          // https://bugs.eclipse.org/bugs/show_bug.cgi?id=223225
          return Collections.singletonList(createAnnotation((IAnnotationBinding) value));
        }
      }
      log.warning("Couldn't find annotation array in: " + binding);
    }
    return Collections.singletonList(createAnnotation(binding));
  }

  /**
   * Given an annotation binding, determine what parser to use for it.
   *
   * @param binding
   * @return
   */
  protected ICrystalAnnotation createAnnotation(IAnnotationBinding binding) {
    String qualName;
    ICrystalAnnotation crystalAnno;

    qualName = binding.getAnnotationType().getQualifiedName();

    crystalAnno = createCrystalAnnotation(binding.getAnnotationType());
    crystalAnno.setName(qualName);

    for (IMemberValuePairBinding pair : binding.getAllMemberValuePairs()) {
      crystalAnno.setObject(pair.getName(), getAnnotationValue(pair.getValue(), pair
          .getMethodBinding().getReturnType().isArray()));
    }
    return crystalAnno;
  }

 
 
  /**
   * Find the type of the given annotation.
   * @param anno The annotation whose type you need.
   * @param relative_type The relative type that we want to use to look up this annotation's type. In the
   * Java model, you can only look up a type wrt another type.
   * @return The type of the given annotation.
   * @throws JavaModelException
   */
  protected IType getTypeOfAnnotation(IAnnotation anno, IType relative_type) throws JavaModelException {
    IJavaProject project = anno.getJavaProject();
    Pair<String,String> name = getQualifiedAnnoType(anno, relative_type);
    IType anno_type = project.findType(name.fst(), name.snd());
   
    return anno_type;
  }
 
  /**
   * Returns the fully qualified type name, as a package/type pair, for the given annotation relative
   * to the relative type.
   */
  protected Pair<String,String> getQualifiedAnnoType(IAnnotation anno, IType relative_type) throws JavaModelException {
    String[][] names = relative_type.resolveType(anno.getElementName());
   
    if( names.length > 1 ) throw new RuntimeException("Not yet implemented.");
   
    String pack = names[0][0];
    String clazz = names[0][1];
    return Pair.create(pack, clazz);
  }
 
  /**
   * See {@link #createAnnotation(IAnnotationBinding)}.
   * @throws JavaModelException
   */
  protected ICrystalAnnotation createAnnotation(IAnnotation anno, IType relative_type) throws JavaModelException {
    String qualName;
    ICrystalAnnotation crystalAnno;

    Pair<String,String> qual_name_ = getQualifiedAnnoType(anno, relative_type);
    qualName = "".equals(qual_name_.fst()) ? qual_name_.snd() : qual_name_.fst() + "." + qual_name_.snd();

    IType anno_type = getTypeOfAnnotation(anno, relative_type);
    crystalAnno = createCrystalAnnotation(anno_type);
    crystalAnno.setName(qualName);

    // These members have a value that is not default.
    Set<String> has_non_default_value = new HashSet<String>();
   
    for (IMemberValuePair pair : anno.getMemberValuePairs()) {
      boolean val_is_array = pair.getValue() instanceof Object[];
     
      has_non_default_value.add(pair.getMemberName());
      crystalAnno.setObject(pair.getMemberName(),
          getAnnotationValue(pair.getValue(), val_is_array));
    }
   
    // Now, for every default that we have not already seen a value for
    // put the default value in.
    for( IMemberValuePair pair : findAnnotationDefaults(anno_type) ) {
      boolean val_is_array = pair.getValue() instanceof Object[];
     
      if( !has_non_default_value.contains(pair.getMemberName()) ) {
        crystalAnno.setObject(pair.getMemberName(),
            getAnnotationValue(pair.getValue(), val_is_array));
      }
    }
   
    return crystalAnno;
  }
 
  /**
   * Returns the default annotation values for the given annotation.
   * @throws JavaModelException
   */
  private List<IMemberValuePair> findAnnotationDefaults(IType anno_type) throws JavaModelException {
    IMethod[] properties = anno_type.getMethods();
    List<IMemberValuePair> result = new ArrayList<IMemberValuePair>();
   
    for( IMethod property : properties ) {
      IMemberValuePair default_val_ = property.getDefaultValue();
      if( default_val_ != null )
        result.add(default_val_);
    }
   
    return result;
  }

  /**
   * Checks whether this annotation is marked as a multi annotation, as described by
   * MultiAnnotation
   *
   * @param annoBinding
   * @return true id this is a multi annotation, and false if it is not.
   */
  public boolean isMulti(IAnnotationBinding annoBinding) {
    ITypeBinding binding = annoBinding.getAnnotationType();

    for (IAnnotationBinding meta : binding.getAnnotations()) {
      if (meta.getAnnotationType().getQualifiedName().equals(MULTI_ANNOTATION_CLASSNAME))
        return true;
    }
    return false;
  }

  /**
   * See {@link #isMulti(IAnnotationBinding)}.
   * @throws JavaModelException
   */
  public boolean isMulti(IAnnotation anno, IType relative_type) throws JavaModelException {
    // First we have to go from this particular annotation, to its declaration...
    IType anno_type = getTypeOfAnnotation(anno, relative_type);

    for (IAnnotation meta : anno_type.getAnnotations()) {
      Pair<String,String> qual_name_ = getQualifiedAnnoType(meta, anno_type);
      String qual_name = "".equals(qual_name_.fst()) ? qual_name_.snd() : qual_name_.fst() + "." + qual_name_.snd();
      if (qual_name.equals(MULTI_ANNOTATION_CLASSNAME))
        return true;
    }
    return false;
  }
 
  /**
   * @param value
   * @return
   */
  private Object getAnnotationValue(Object rawValue, boolean forceArray) {
    if (rawValue instanceof Object[]) {
      Object[] array = (Object[]) rawValue;
      Object[] result = new Object[array.length];
      for (int i = 0; i < array.length; i++)
        result[i] = getAnnotationValue(array[i], false);
      return result;
    }
    if (rawValue instanceof IAnnotationBinding) {
      rawValue = createAnnotation((IAnnotationBinding) rawValue);
    }
    if (forceArray) {
      // this is a workaround for an Eclipse "bug" (#223225)
      // Eclipse doesn't desugar single-element arrays with omitted braces as arrays
      // https://bugs.eclipse.org/bugs/show_bug.cgi?id=223225
      return new Object[] { rawValue };
    }
    // other values are literals
    return rawValue;
  }

  /**
   * See {@link #createCrystalAnnotation(ITypeBinding)}.
   * @throws JavaModelException
   */
  public ICrystalAnnotation createCrystalAnnotation(IType typeOfAnnotation) throws JavaModelException {
    Class<? extends ICrystalAnnotation> annoClass =
        qualNames.get(typeOfAnnotation.getFullyQualifiedName('.'));
   
    if (annoClass == null) {
      IAnnotation[] metas = typeOfAnnotation.getAnnotations();
      // might still be a meta annotation. Check for this.
      for (int ndx = 0; ndx < metas.length && annoClass == null; ndx++) {
        IAnnotation meta = metas[ndx];
       
        Pair<String,String> meta_name = getQualifiedAnnoType(meta, typeOfAnnotation);
        String meta_name_ = meta_name.fst() + "." + meta_name.snd();
       
        annoClass = metaQualNames.get(meta_name_);
      }
    }

    if (annoClass == null)
      return new CrystalAnnotation();

    try {
      // this annotation is registered directly
      return annoClass.newInstance();
    }
    catch (InstantiationException e) {
      log.log(
          Level.WARNING,
          "Error instantiating custom annotation parser.  Using default representation.", e);
      return new CrystalAnnotation();
    }
    catch (IllegalAccessException e) {
      log.log(
          Level.WARNING,
          "Error accessing custom annotation parser.  Using default representation.", e);
      return new CrystalAnnotation();
    }
  }
 
  public ICrystalAnnotation createCrystalAnnotation(ITypeBinding typeBinding) {
    Class<? extends ICrystalAnnotation> annoClass =
        qualNames.get(typeBinding.getQualifiedName());

    if (annoClass == null) {
      IAnnotationBinding[] metas = typeBinding.getAnnotations();
      // might still be a meta annotation. Check for this.
      for (int ndx = 0; ndx < metas.length && annoClass == null; ndx++) {
        IAnnotationBinding meta = metas[ndx];
        String metaName = meta.getAnnotationType().getQualifiedName();
        annoClass = metaQualNames.get(metaName);
      }
    }

    if (annoClass == null)
      return new CrystalAnnotation();

    try {
      // this annotation is registered directly
      return annoClass.newInstance();
    }
    catch (InstantiationException e) {
      log.log(
          Level.WARNING,
          "Error instantiating custom annotation parser.  Using default representation.", e);
      return new CrystalAnnotation();
    }
    catch (IllegalAccessException e) {
      log.log(
          Level.WARNING,
          "Error accessing custom annotation parser.  Using default representation.", e);
      return new CrystalAnnotation();
    }
  }

  public void addAnnotationToField(ICrystalAnnotation anno, FieldDeclaration field) {
    IVariableBinding binding;
    String name;
    List<ICrystalAnnotation> annoList;

    if (field.fragments().isEmpty())
      return;

    binding = ((VariableDeclarationFragment) field.fragments().get(0)).resolveBinding();
    name = binding.getKey();
    annoList = fields.get(name);
    if (annoList == null) {
      annoList = new ArrayList<ICrystalAnnotation>();
      fields.put(name, annoList);
    }
    annoList.add(anno);
  }

  public void addAnnotationToMethod(AnnotationSummary anno, MethodDeclaration method) {
    IMethodBinding binding = method.resolveBinding();
    String name = binding.getKey();

    AnnotationSummary existing = methods.get(name);
    if (existing == null)
      methods.put(name, anno);
    else
      existing.add(anno);
  }

  public void addAnnotationToType(ICrystalAnnotation anno, TypeDeclaration type) {
    ITypeBinding binding = type.resolveBinding();
    String name = binding.getKey();
    List<ICrystalAnnotation> annoList;

    annoList = classes.get(name);
    if (annoList == null) {
      annoList = new ArrayList<ICrystalAnnotation>();
      classes.put(name, annoList);
    }
    annoList.add(anno);
  }

  public static <A extends ICrystalAnnotation> List<A> filter(List<ICrystalAnnotation> list,
      Class<A> type) {
    List<A> result = new LinkedList<A>();
    for (ICrystalAnnotation anno : list) {
      if (type.isAssignableFrom(anno.getClass())) {
        @SuppressWarnings("unchecked") A anno2 = (A) anno;
        result.add(anno2);
      }
    }
    return result;
  }

  static protected ICrystalAnnotation findAnnotation(String name, List<ICrystalAnnotation> list) {
    ICrystalAnnotation result = null;
    for (ICrystalAnnotation anno : list) {
      if (anno.getName().equals(name)) {
        assert result == null;
        result = anno;
      }
    }
    return result;
  }
}
TOP

Related Classes of edu.cmu.cs.crystal.annotations.AnnotationDatabase

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.