| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * 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.android.tools.lint.client.api; |
| |
| import static com.android.SdkConstants.ATTR_VALUE; |
| |
| import com.android.annotations.NonNull; |
| import com.android.annotations.Nullable; |
| import com.android.tools.lint.detector.api.Context; |
| import com.android.tools.lint.detector.api.JavaContext; |
| import com.android.tools.lint.detector.api.Location; |
| import com.google.common.annotations.Beta; |
| import com.google.common.base.Splitter; |
| |
| import java.util.Collections; |
| import java.util.List; |
| |
| import lombok.ast.Identifier; |
| import lombok.ast.Node; |
| import lombok.ast.StrictListAccessor; |
| import lombok.ast.TypeReference; |
| import lombok.ast.TypeReferencePart; |
| |
| /** |
| * A wrapper for a Java parser. This allows tools integrating lint to map directly |
| * to builtin services, such as already-parsed data structures in Java editors. |
| * <p/> |
| * <b>NOTE: This is not public or final API; if you rely on this be prepared |
| * to adjust your code for the next tools release.</b> |
| */ |
| @Beta |
| public abstract class JavaParser { |
| public static final String TYPE_OBJECT = "java.lang.Object"; //$NON-NLS-1$ |
| public static final String TYPE_STRING = "java.lang.String"; //$NON-NLS-1$ |
| public static final String TYPE_INT = "int"; //$NON-NLS-1$ |
| public static final String TYPE_LONG = "long"; //$NON-NLS-1$ |
| public static final String TYPE_CHAR = "char"; //$NON-NLS-1$ |
| public static final String TYPE_FLOAT = "float"; //$NON-NLS-1$ |
| public static final String TYPE_DOUBLE = "double"; //$NON-NLS-1$ |
| public static final String TYPE_BOOLEAN = "boolean"; //$NON-NLS-1$ |
| public static final String TYPE_SHORT = "short"; //$NON-NLS-1$ |
| public static final String TYPE_BYTE = "byte"; //$NON-NLS-1$ |
| public static final String TYPE_NULL = "null"; //$NON-NLS-1$ |
| |
| /** |
| * Prepare to parse the given contexts. This method will be called before |
| * a series of {@link #parseJava(JavaContext)} calls, which allows some |
| * parsers to do up front global computation in case they want to more |
| * efficiently process multiple files at the same time. This allows a single |
| * type-attribution pass for example, which is a lot more efficient than |
| * performing global type analysis over and over again for each individual |
| * file |
| * |
| * @param contexts a list of contexts to be parsed |
| */ |
| public abstract void prepareJavaParse(@NonNull List<JavaContext> contexts); |
| |
| /** |
| * Parse the file pointed to by the given context. |
| * |
| * @param context the context pointing to the file to be parsed, typically |
| * via {@link Context#getContents()} but the file handle ( |
| * {@link Context#file} can also be used to map to an existing |
| * editor buffer in the surrounding tool, etc) |
| * @return the compilation unit node for the file |
| */ |
| @Nullable |
| public abstract Node parseJava(@NonNull JavaContext context); |
| |
| /** |
| * Returns a {@link Location} for the given node |
| * |
| * @param context information about the file being parsed |
| * @param node the node to create a location for |
| * @return a location for the given node |
| */ |
| @NonNull |
| public abstract Location getLocation(@NonNull JavaContext context, @NonNull Node node); |
| |
| /** |
| * Creates a light-weight handle to a location for the given node. It can be |
| * turned into a full fledged location by |
| * {@link com.android.tools.lint.detector.api.Location.Handle#resolve()}. |
| * |
| * @param context the context providing the node |
| * @param node the node (element or attribute) to create a location handle |
| * for |
| * @return a location handle |
| */ |
| @NonNull |
| public abstract Location.Handle createLocationHandle(@NonNull JavaContext context, |
| @NonNull Node node); |
| |
| /** |
| * Dispose any data structures held for the given context. |
| * @param context information about the file previously parsed |
| * @param compilationUnit the compilation unit being disposed |
| */ |
| public void dispose(@NonNull JavaContext context, @NonNull Node compilationUnit) { |
| } |
| |
| /** |
| * Dispose any remaining data structures held for all contexts. |
| * Typically frees up any resources allocated by |
| * {@link #prepareJavaParse(List)} |
| */ |
| public void dispose() { |
| } |
| |
| /** |
| * Resolves the given expression node: computes the declaration for the given symbol |
| * |
| * @param context information about the file being parsed |
| * @param node the node to resolve |
| * @return a node representing the resolved fully type: class/interface/annotation, |
| * field, method or variable |
| */ |
| @Nullable |
| public abstract ResolvedNode resolve(@NonNull JavaContext context, @NonNull Node node); |
| |
| /** |
| * Finds the given type, if possible (which should be reachable from the compilation |
| * patch of the given node. |
| * |
| * @param context information about the file being parsed |
| * @param fullyQualifiedName the fully qualified name of the class to look up |
| * @return the class, or null if not found |
| */ |
| @Nullable |
| public ResolvedClass findClass( |
| @NonNull JavaContext context, |
| @NonNull String fullyQualifiedName) { |
| return null; |
| } |
| |
| /** |
| * Gets the type of the given node |
| * |
| * @param context information about the file being parsed |
| * @param node the node to look up the type for |
| * @return the type of the node, if known |
| */ |
| @Nullable |
| public abstract TypeDescriptor getType(@NonNull JavaContext context, @NonNull Node node); |
| |
| /** A description of a type, such as a primitive int or the android.app.Activity class */ |
| public abstract static class TypeDescriptor { |
| /** |
| * Returns the fully qualified name of the type, such as "int" or "android.app.Activity" |
| * */ |
| @NonNull public abstract String getName(); |
| |
| /** |
| * Returns the full signature of the type, which is normally the same as {@link #getName()} |
| * but for arrays can include []'s, for generic methods can include generics parameters |
| * etc |
| */ |
| @NonNull public abstract String getSignature(); |
| |
| public abstract boolean matchesName(@NonNull String name); |
| |
| public abstract boolean matchesSignature(@NonNull String signature); |
| |
| @NonNull |
| public TypeReference getNode() { |
| TypeReference typeReference = new TypeReference(); |
| StrictListAccessor<TypeReferencePart, TypeReference> parts = typeReference.astParts(); |
| for (String part : Splitter.on('.').split(getName())) { |
| Identifier identifier = Identifier.of(part); |
| parts.addToEnd(new TypeReferencePart().astIdentifier(identifier)); |
| } |
| |
| return typeReference; |
| } |
| |
| /** If the type is not primitive, returns the class of the type if known */ |
| @Nullable |
| public abstract ResolvedClass getTypeClass(); |
| |
| @Override |
| public abstract boolean equals(Object o); |
| |
| } |
| |
| /** Convenience implementation of {@link TypeDescriptor} */ |
| public static class DefaultTypeDescriptor extends TypeDescriptor { |
| |
| private String mName; |
| |
| public DefaultTypeDescriptor(String name) { |
| mName = name; |
| } |
| |
| @NonNull |
| @Override |
| public String getName() { |
| return mName; |
| } |
| |
| @NonNull |
| @Override |
| public String getSignature() { |
| return getName(); |
| } |
| |
| @Override |
| public boolean matchesName(@NonNull String name) { |
| return mName.equals(name); |
| } |
| |
| @Override |
| public boolean matchesSignature(@NonNull String signature) { |
| return matchesName(signature); |
| } |
| |
| @Override |
| public String toString() { |
| return getSignature(); |
| } |
| |
| @Override |
| @Nullable |
| public ResolvedClass getTypeClass() { |
| return null; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (o == null || getClass() != o.getClass()) { |
| return false; |
| } |
| |
| DefaultTypeDescriptor that = (DefaultTypeDescriptor) o; |
| |
| return !(mName != null ? !mName.equals(that.mName) : that.mName != null); |
| |
| } |
| |
| @Override |
| public int hashCode() { |
| return mName != null ? mName.hashCode() : 0; |
| } |
| } |
| |
| /** A resolved declaration from an AST Node reference */ |
| public abstract static class ResolvedNode { |
| @NonNull |
| public abstract String getName(); |
| |
| /** Returns the signature of the resolved node */ |
| public abstract String getSignature(); |
| |
| public abstract int getModifiers(); |
| |
| @Override |
| public String toString() { |
| return getSignature(); |
| } |
| |
| /** Returns any annotations defined on this node */ |
| @NonNull |
| public abstract Iterable<ResolvedAnnotation> getAnnotations(); |
| |
| /** |
| * Searches for the annotation of the given type on this node |
| * |
| * @param type the fully qualified name of the annotation to check |
| * @return the annotation, or null if not found |
| */ |
| @Nullable |
| public ResolvedAnnotation getAnnotation(@NonNull String type) { |
| for (ResolvedAnnotation annotation : getAnnotations()) { |
| if (annotation.getType().matchesSignature(type)) { |
| return annotation; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns true if this element is in the given package (or optionally, in one of its sub |
| * packages) |
| * |
| * @param pkg the package name |
| * @param includeSubPackages whether to include subpackages |
| * @return true if the element is in the given package |
| */ |
| public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) { |
| return getSignature().startsWith(pkg); |
| } |
| } |
| |
| /** A resolved class declaration (class, interface, enumeration or annotation) */ |
| public abstract static class ResolvedClass extends ResolvedNode { |
| /** Returns the fully qualified name of this class */ |
| @Override |
| @NonNull |
| public abstract String getName(); |
| |
| /** Returns the simple of this class */ |
| @NonNull |
| public abstract String getSimpleName(); |
| |
| /** Returns the package name of this class */ |
| @NonNull |
| public String getPackageName() { |
| String name = getName(); |
| String simpleName = getSimpleName(); |
| if (name.length() > simpleName.length() + 1) { |
| return name.substring(0, name.length() - simpleName.length() - 1); |
| } |
| return name; |
| } |
| |
| /** Returns whether this class' fully qualified name matches the given name */ |
| public abstract boolean matches(@NonNull String name); |
| |
| @Nullable |
| public abstract ResolvedClass getSuperClass(); |
| |
| @Nullable |
| public abstract ResolvedClass getContainingClass(); |
| |
| public TypeDescriptor getType() { |
| return new DefaultTypeDescriptor(getName()); |
| } |
| |
| /** |
| * Determines whether this class extends the given name. If strict is true, |
| * it will not consider C extends C true. |
| * |
| * @param name the fully qualified class name |
| * @param strict if true, do not consider a class to be extending itself |
| * @return true if this class extends the given class |
| */ |
| public abstract boolean isSubclassOf(@NonNull String name, boolean strict); |
| |
| @NonNull |
| public abstract Iterable<ResolvedMethod> getConstructors(); |
| |
| /** Returns the methods defined in this class, and optionally any methods inherited from any superclasses as well */ |
| @NonNull |
| public abstract Iterable<ResolvedMethod> getMethods(boolean includeInherited); |
| |
| /** Returns the methods of a given name defined in this class, and optionally any methods inherited from any superclasses as well */ |
| @NonNull |
| public abstract Iterable<ResolvedMethod> getMethods(@NonNull String name, boolean includeInherited); |
| |
| /** Returns the fields defined in this class, and optionally any fields declared in any superclasses as well */ |
| @NonNull |
| public abstract Iterable<ResolvedField> getFields(boolean includeInherited); |
| |
| /** Returns the named field defined in this class, or optionally inherited from a superclass */ |
| @Nullable |
| public abstract ResolvedField getField(@NonNull String name, boolean includeInherited); |
| |
| /** Returns the package containing this class */ |
| @Nullable |
| public abstract ResolvedPackage getPackage(); |
| |
| @Override |
| public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) { |
| String packageName = getPackageName(); |
| |
| //noinspection SimplifiableIfStatement |
| if (pkg.equals(packageName)) { |
| return true; |
| } |
| |
| return includeSubPackages && packageName.length() > pkg.length() && |
| packageName.charAt(pkg.length()) == '.' && |
| packageName.startsWith(pkg); |
| } |
| } |
| |
| /** A method or constructor declaration */ |
| public abstract static class ResolvedMethod extends ResolvedNode { |
| @Override |
| @NonNull |
| public abstract String getName(); |
| |
| /** Returns whether this method name matches the given name */ |
| public abstract boolean matches(@NonNull String name); |
| |
| @NonNull |
| public abstract ResolvedClass getContainingClass(); |
| |
| public abstract int getArgumentCount(); |
| |
| @NonNull |
| public abstract TypeDescriptor getArgumentType(int index); |
| |
| /** Returns true if the parameter at the given index matches the given type signature */ |
| public boolean argumentMatchesType(int index, @NonNull String signature) { |
| return getArgumentType(index).matchesSignature(signature); |
| } |
| |
| @Nullable |
| public abstract TypeDescriptor getReturnType(); |
| |
| public boolean isConstructor() { |
| return getReturnType() == null; |
| } |
| |
| /** Returns any annotations defined on the given parameter of this method */ |
| @NonNull |
| public abstract Iterable<ResolvedAnnotation> getParameterAnnotations(int index); |
| |
| /** |
| * Searches for the annotation of the given type on the method |
| * |
| * @param type the fully qualified name of the annotation to check |
| * @param parameterIndex the index of the parameter to look up |
| * @return the annotation, or null if not found |
| */ |
| @Nullable |
| public ResolvedAnnotation getParameterAnnotation(@NonNull String type, |
| int parameterIndex) { |
| for (ResolvedAnnotation annotation : getParameterAnnotations(parameterIndex)) { |
| if (annotation.getType().matchesSignature(type)) { |
| return annotation; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** Returns the super implementation of the given method, if any */ |
| @Nullable |
| public ResolvedMethod getSuperMethod() { |
| ResolvedClass cls = getContainingClass().getSuperClass(); |
| if (cls != null) { |
| String methodName = getName(); |
| int argCount = getArgumentCount(); |
| for (ResolvedMethod method : cls.getMethods(methodName, true)) { |
| if (argCount != method.getArgumentCount()) { |
| continue; |
| } |
| boolean sameTypes = true; |
| for (int arg = 0; arg < argCount; arg++) { |
| if (!method.getArgumentType(arg).equals(getArgumentType(arg))) { |
| sameTypes = false; |
| break; |
| } |
| } |
| if (sameTypes) { |
| return method; |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| @Override |
| public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) { |
| String packageName = getContainingClass().getPackageName(); |
| |
| //noinspection SimplifiableIfStatement |
| if (pkg.equals(packageName)) { |
| return true; |
| } |
| |
| return includeSubPackages && packageName.length() > pkg.length() && |
| packageName.charAt(pkg.length()) == '.' && |
| packageName.startsWith(pkg); |
| } |
| } |
| |
| /** A field declaration */ |
| public abstract static class ResolvedField extends ResolvedNode { |
| @Override |
| @NonNull |
| public abstract String getName(); |
| |
| /** Returns whether this field name matches the given name */ |
| public abstract boolean matches(@NonNull String name); |
| |
| @NonNull |
| public abstract TypeDescriptor getType(); |
| |
| @NonNull |
| public abstract ResolvedClass getContainingClass(); |
| |
| @Nullable |
| public abstract Object getValue(); |
| |
| public String getContainingClassName() { |
| return getContainingClass().getName(); |
| } |
| |
| @Override |
| public boolean isInPackage(@NonNull String pkg, boolean includeSubPackages) { |
| String packageName = getContainingClass().getPackageName(); |
| |
| //noinspection SimplifiableIfStatement |
| if (pkg.equals(packageName)) { |
| return true; |
| } |
| |
| return includeSubPackages && packageName.length() > pkg.length() && |
| packageName.charAt(pkg.length()) == '.' && |
| packageName.startsWith(pkg); |
| } |
| } |
| |
| /** |
| * An annotation <b>reference</b>. Note that this refers to a usage of an annotation, |
| * not a declaraton of an annotation. You can call {@link #getClassType()} to |
| * find the declaration for the annotation. |
| */ |
| public abstract static class ResolvedAnnotation extends ResolvedNode { |
| @Override |
| @NonNull |
| public abstract String getName(); |
| |
| /** Returns whether this field name matches the given name */ |
| public abstract boolean matches(@NonNull String name); |
| |
| @NonNull |
| public abstract TypeDescriptor getType(); |
| |
| /** Returns the {@link ResolvedClass} which defines the annotation */ |
| @Nullable |
| public abstract ResolvedClass getClassType(); |
| |
| public static class Value { |
| @NonNull public final String name; |
| @Nullable public final Object value; |
| |
| public Value(@NonNull String name, @Nullable Object value) { |
| this.name = name; |
| this.value = value; |
| } |
| } |
| |
| @NonNull |
| public abstract List<Value> getValues(); |
| |
| @Nullable |
| public Object getValue(@NonNull String name) { |
| for (Value value : getValues()) { |
| if (name.equals(value.name)) { |
| return value.value; |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| public Object getValue() { |
| return getValue(ATTR_VALUE); |
| } |
| |
| @NonNull |
| @Override |
| public Iterable<ResolvedAnnotation> getAnnotations() { |
| return Collections.emptyList(); |
| } |
| } |
| |
| /** A package declaration */ |
| public abstract static class ResolvedPackage extends ResolvedNode { |
| @NonNull |
| @Override |
| public Iterable<ResolvedAnnotation> getAnnotations() { |
| return Collections.emptyList(); |
| } |
| } |
| |
| /** A local variable or parameter declaration */ |
| public abstract static class ResolvedVariable extends ResolvedNode { |
| @Override |
| @NonNull |
| public abstract String getName(); |
| |
| /** Returns whether this variable name matches the given name */ |
| public abstract boolean matches(@NonNull String name); |
| |
| @NonNull |
| public abstract TypeDescriptor getType(); |
| } |
| } |