/* * Copyright (C) 2013 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 org.jetbrains.android.inspections.lint; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.tools.lint.checks.RegistrationDetector; import com.android.tools.lint.detector.api.*; import com.intellij.openapi.application.ApplicationManager; import com.intellij.psi.*; import lombok.ast.AstVisitor; import lombok.ast.CompilationUnit; import lombok.ast.ForwardingAstVisitor; import lombok.ast.Node; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; import java.util.List; import static com.android.SdkConstants.*; /** * Intellij-specific version of the {@link com.android.tools.lint.checks.RegistrationDetector} which uses the PSI structure * to check classes. * <p> * <ul> * <li>Unit tests, and compare to the bytecode based results</li> * </ul> */ public class IntellijRegistrationDetector extends RegistrationDetector implements Detector.JavaScanner { static final Implementation IMPLEMENTATION = new Implementation( IntellijRegistrationDetector.class, EnumSet.of(Scope.MANIFEST, Scope.JAVA_FILE)); @Nullable @Override public List<Class<? extends Node>> getApplicableNodeTypes() { return Collections.<Class<? extends Node>>singletonList(CompilationUnit.class); } @Nullable @Override public AstVisitor createJavaVisitor(@NonNull final JavaContext context) { return new ForwardingAstVisitor() { @Override public boolean visitCompilationUnit(CompilationUnit node) { check(context); return true; } }; } private void check(final JavaContext context) { ApplicationManager.getApplication().runReadAction(new Runnable() { @Override public void run() { final PsiFile psiFile = IntellijLintUtils.getPsiFile(context); if (!(psiFile instanceof PsiJavaFile)) { return; } PsiJavaFile javaFile = (PsiJavaFile)psiFile; for (PsiClass clz : javaFile.getClasses()) { check(context, clz); } } }); } private void check(JavaContext context, PsiClass clz) { for (PsiClass current = clz.getSuperClass(); current != null; current = current.getSuperClass()) { // Ignore abstract classes if (clz.hasModifierProperty(PsiModifier.ABSTRACT) || clz instanceof PsiAnonymousClass) { continue; } String fqcn = current.getQualifiedName(); if (fqcn == null) { continue; } if (CLASS_ACTIVITY.equals(fqcn) || CLASS_SERVICE.equals(fqcn) || CLASS_CONTENTPROVIDER.equals(fqcn)) { String internalName = IntellijLintUtils.getInternalName(clz); if (internalName == null) { continue; } String frameworkClass = ClassContext.getInternalName(fqcn); Collection<String> registered = mManifestRegistrations != null ? mManifestRegistrations.get(frameworkClass) : null; if (registered == null || !registered.contains(internalName)) { report(context, clz, frameworkClass); } break; } } for (PsiClass innerClass : clz.getInnerClasses()) { check(context, innerClass); } } private static void report(JavaContext context, PsiClass clz, String internalName) { // Unlike the superclass, we don't have to check that the tags are compatible; // IDEA already checks that as part of its XML validation if (IntellijLintUtils.isSuppressed(clz, clz.getContainingFile(), ISSUE)) { return; } String tag = classToTag(internalName); Location location = IntellijLintUtils.getLocation(context.file, clz); String fqcn = clz.getQualifiedName(); if (fqcn == null) { fqcn = clz.getName(); } context.report(ISSUE, location, String.format("The <%1$s> %2$s is not registered in the manifest", tag, fqcn), null); } }