Package lombok.ast.resolve

Source Code of lombok.ast.resolve.Resolver

/*
* Copyright (C) 2009-2011 The Project Lombok Authors.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package lombok.ast.resolve;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import lombok.ast.Annotation;
import lombok.ast.Block;
import lombok.ast.CompilationUnit;
import lombok.ast.Expression;
import lombok.ast.Identifier;
import lombok.ast.ImportDeclaration;
import lombok.ast.Node;
import lombok.ast.NullLiteral;
import lombok.ast.PackageDeclaration;
import lombok.ast.RawListAccessor;
import lombok.ast.ResolutionException;
import lombok.ast.Select;
import lombok.ast.TypeBody;
import lombok.ast.TypeDeclaration;
import lombok.ast.TypeReference;

import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;

/**
* Contains simplistic (guesstimations) resolution that doesn't require full resolution and symbol lookup but it isn't perfect.
*/
public class Resolver {
  /**
   * If {@code value} is an enum constant of type {@code enumClass}, then the enum value will be returned.
   * {@code null} will be returned if {@code value} is an actual null literal.
   *
   * @throws ResolutionException If {@code value} cannot be converted.
   */
  public <E extends Enum<E>> E resolveEnum(Class<E> enumClass, Node value) {
    // case1: null
    if (value instanceof NullLiteral) return null;
   
    // case2: Identifier
    String enumName = null;
   
    if (value instanceof Identifier) {
      enumName = ((Identifier)value).astValue();
    }
   
    // case3: EnumSimpleName.Identifier or EnumFQN.Identifier
    String typeName = null;
    String packageName = null;
    if (value instanceof Select) {
      List<String> chain = unwrapSelectChain((Select) value);
      switch (chain.size()) {
      case 0:
        throw new ResolutionException(value, "empty");
      default:
        packageName = Joiner.on('.').join(chain.subList(0, chain.size() - 2));
      case 2:
        typeName = chain.get(chain.size() - 2);
      case 1:
        enumName = chain.get(chain.size() - 1);
      }
     
      boolean unexpectedType = false;
      if (packageName != null) {
        Package p = enumClass.getPackage();
        unexpectedType = p != null && !p.getName().equals(packageName);
      }
      unexpectedType |= (typeName != null && !enumClass.getSimpleName().equals(typeName));
     
      if (unexpectedType) throw new ResolutionException(value, "Expected " + enumClass.getName() + " and not " + packageName + "." + typeName);
    }
   
    for (E enumConstant : enumClass.getEnumConstants()) {
      String target = enumConstant.name();
      if (target.equals(enumName)) return enumConstant;
    }
   
    throw new ResolutionException(value, "Not a valid value for enum " + enumClass.getSimpleName() + ": " + enumName);
  }
 
  static final List<Class<?>> NUMERIC_PRIMITIVE_CLASSES = ImmutableList.<Class<?>>of(
      long.class, int.class, short.class, byte.class, double.class, float.class, char.class);
  static final Map<String, Class<?>> PRIMITIVE_CLASS_MAP = ImmutableMap.<String, Class<?>>builder()
      .put("boolean", boolean.class)
      .put("byte", byte.class)
      .put("short", short.class)
      .put("int", int.class)
      .put("long", long.class)
      .put("char", char.class)
      .put("float", float.class)
      .put("double", double.class)
      .build();
 
  private static class ImportList {
    final List<String> explicits = new ArrayList<String>();
    final List<String> stars = new ArrayList<String>();
   
    ImportList() {
      stars.add("java.lang");
    }
  }
 
  private ImportList getImportList(Node n) {
    ImportList il = new ImportList();
   
    while (n != null) {
      if (n instanceof CompilationUnit) {
        CompilationUnit cu = (CompilationUnit) n;
        PackageDeclaration pkg = cu.astPackageDeclaration();
        if (pkg != null) il.stars.add(pkg.getPackageName());
        for (ImportDeclaration imp : cu.astImportDeclarations()) {
          if (imp.astStaticImport()) continue;
          if (imp.astStarImport()) {
            String i = imp.asFullyQualifiedName();
            i = i.substring(0, i.length() - 2);
            il.stars.add(i);
          } else {
            il.explicits.add(imp.asFullyQualifiedName());
          }
        }
      }
     
      n = n.getParent();
    }
   
    return il;
  }
 
  /**
   * Checks if the given {@code typeReference} could legally be referring to the listed fully qualified {@code typeName}.
   * Will check import statements, and checks for any shadowing by types in this file with the same name.
   * <p>
   * <em>WARNING:</em> This method will return {@code true} if there's a star import of i.e. {@code java.util.*},
   * you ask if {@code ArrayList} could be referring to {@code java.util.ArrayList}, and there is another class in the same package also named
   * {@code ArrayList}. The right answer is of course {@code false}, but without a classpath/sourcepath this cannot be determined. Therefore,
   * this method is 99% but not 100% accurate. Also, god kills a puppy everytime you write a star import.
   * <p>
   * <em>NB:</em> Any type arguments (generics) on either the {@code typeReference} or {@code wanted} are stripped off before comparing the two.
   * <p>
   * <em>NB:</em> If you want array types, just put array brackets at the end of your {@code wanted} string, for example: "{@code java.lang.String[]}".
   *
   * @param wanted A fully qualified type name, such as {@code java.lang.String}.
   * @param typeReference A type reference node.
   * @return {@code true} if the typeReference is most likely a reference to {@code wanted}.
   */
  public boolean typesMatch(String wanted, TypeReference typeReference) {
    String name = stripGenerics(typeReference.getTypeName());
    wanted = stripGenerics(wanted);
   
    if (name.equals(wanted)) return true;
   
    /* check array dimensions */ {
      int dims1 = typeReference.astArrayDimensions();
      int dims2 = 0;
      while (wanted.endsWith("[]")) {
        dims2++;
        wanted = wanted.substring(0, wanted.length() - 2);
      }
      if (dims1 != dims2) return false;
      if (dims1 > 0) name = name.substring(0, name.length() - dims1 * 2);
    }
   
    int dot = wanted.lastIndexOf('.');
    String wantedPkg = dot == -1 ? "" : wanted.substring(0, dot);
    String wantedName = dot == -1 ? wanted : wanted.substring(dot + 1);
   
    // If type ref appears fully qualified and it hasn't matched by now, we're done.
    if (name.indexOf('.') > -1) return false;
   
    // 'Baz' will never be a match for foo.bar.NotBaz.
    if (!wantedName.equals(name)) return false;
   
    ImportList imports = getImportList(typeReference);
   
    /* If matching List to java.util.List and java.awt.List is explicitly imported, no match. */ {
      String ending = "." + wantedName;
      for (String explicit : imports.explicits) {
        if (explicit.endsWith(ending) && !explicit.equals(wanted)) return false;
      }
    }
   
    potentialMatchFound:
    /* To match List to java.util.List either there has to be a java.util.* or java.util.List import. */ {
      if (wantedPkg.length() > 0 && imports.stars.contains(wantedPkg)) break potentialMatchFound;
      if (imports.explicits.contains(wanted)) break potentialMatchFound;
      return false;
    }
   
    //name is definitely a simple name, and it might match. Walk up type tree and if it doesn't match any of those, delve into import statements.
    Node n = typeReference.getParent();
    Node prevN = null;
    CompilationUnit cu = null;
    while (n != null) {
      RawListAccessor<?, ?> list;
      boolean stopAtSelf;
     
      if (n instanceof Block) {
        list = ((Block) n).rawContents();
        stopAtSelf = true;
      } else if (n instanceof TypeBody) {
        list = ((TypeBody) n).rawMembers();
        stopAtSelf = false;
      } else if (n instanceof CompilationUnit) {
        list = ((CompilationUnit) n).rawTypeDeclarations();
        cu = (CompilationUnit) n;
        stopAtSelf = false;
      } else {
        list = null;
        stopAtSelf = false;
      }
     
      if (list != null) {
        for (Node c : list) {
          if (c instanceof TypeDeclaration && namesMatch(name, ((TypeDeclaration) c).astName())) return false;
          if (stopAtSelf && c == prevN) break;
        }
      }
     
      prevN = n;
      n = n.getParent();
    }
   
    //A locally defined type is definitely not what's targeted so it could still be our wanted type reference. Let's check imports.
    if (wantedPkg.isEmpty()) return cu == null || cu.rawPackageDeclaration() == null;
   
    return true;
  }
 
  private String stripGenerics(String name) {
    int genericsStart = name.indexOf('<');
    int genericsEnd = name.lastIndexOf('>');
    if (genericsStart != -1 && genericsEnd == name.length() -1) name = name.substring(0, genericsStart);
    return name;
  }
 
  private boolean namesMatch(String name, Identifier astName) {
    return name == null ? astName.astValue() == null : name.equals(astName.astValue());
  }
 
  /**
   * Turns an annotation AST node into an actual instance of an annotation class provided you already know its type.
   * <strong>NB: non-literal compile-time constants cannot be converted, and you should avoid querying classes;
   * instead call {@code resolver.getAnnotationClassAsString(objectReturnedByThisMethod, "annotation method name")}.
   *
   * @see #getAnnotationClassesAsStrings(java.lang.annotation.Annotation, String)
   * @see #getAnnotationClassAsString(java.lang.annotation.Annotation, String)
   */
  public <A extends java.lang.annotation.Annotation> A toAnnotationInstance(Class<A> type, Annotation node) {
    return type.cast(Proxy.newProxyInstance(type.getClassLoader(), new Class[] { type }, new AnnotationProxy(this, node)));
  }
 
  /**
   * Use together with {@link #toAnnotationInstance(Class, Annotation)}.
   *
   * @see #getAnnotationClassesAsStrings(java.lang.annotation.Annotation, String)
   */
  public String getAnnotationClassAsString(java.lang.annotation.Annotation annotation, String methodName) {
    try {
      Method m = annotation.getClass().getMethod(methodName);
      if (m.getReturnType() != Class.class) throw new IllegalArgumentException("Method " + methodName + " does not have 'Class' as return type");
      try {
        return Class.class.cast(m.invoke(annotation)).toString();
      } catch (AnnotationClassNotAvailableException e) {
        return e.getClassName();
      }
    } catch (IllegalAccessException e) {
      throw new IllegalArgumentException("Method " + methodName + " isn't accessible", e);
    } catch (InvocationTargetException e) {
      throw new IllegalArgumentException("Method " + methodName + " cannot be invoked", e);
    } catch (NoSuchMethodException e) {
      throw new IllegalArgumentException("Method " + methodName + " does not exist");
    }
  }
 
  /**
   * Use together with {@link #toAnnotationInstance(Class, Annotation)}.
   *
   * @see #getAnnotationClassAsString(java.lang.annotation.Annotation, String)
   */
  public List<String> getAnnotationClassesAsStrings(java.lang.annotation.Annotation annotation, String methodName) {
    try {
      Method m = annotation.getClass().getMethod(methodName);
      boolean array;
      if (m.getReturnType() == Class.class) array = false;
      else if (m.getReturnType() == Class[].class) array = true;
      else throw new IllegalArgumentException("Method " + methodName + " does not have 'Class' or 'Class[]' as return type");
     
      try {
        Class<?>[] cs;
        if (array) {
          cs = Class[].class.cast(m.invoke(annotation));
        } else {
          cs = new Class[1];
          cs[0] = Class.class.cast(m.invoke(annotation));
        }
       
        List<String> result = Lists.newArrayList();
        for (Class<?> c : cs) result.add(c.getName());
        return result;
      } catch (AnnotationClassNotAvailableException e) {
        return e.getClassNames();
      }
    } catch (IllegalAccessException e) {
      throw new IllegalArgumentException("Method " + methodName + " isn't accessible", e);
    } catch (InvocationTargetException e) {
      throw new IllegalArgumentException("Method " + methodName + " cannot be invoked", e);
    } catch (NoSuchMethodException e) {
      throw new IllegalArgumentException("Method " + methodName + " does not exist");
    }
  }
 
  private List<String> unwrapSelectChain(Select s) {
    List<String> list = Lists.newArrayList();
    while (s != null) {
      list.add(s.astIdentifier().astValue());
      Expression parent = s.astOperand();
      if (parent instanceof Select) {
        s = (Select) parent;
      } else if (parent instanceof Identifier) {
        s = null;
        list.add(((Identifier)parent).astValue());
      } else if (parent == null) {
        break;
      } else {
        throw new ResolutionException(parent, "Identifies expected here, not a " + parent.getClass().getSimpleName());
      }
    }
   
    Collections.reverse(list);
    return list;
  }
}
TOP

Related Classes of lombok.ast.resolve.Resolver

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.