/* * 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 com.android.tools.lint.checks; import static com.android.tools.lint.client.api.JavaParser.ResolvedClass; import static com.android.tools.lint.client.api.JavaParser.ResolvedField; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.tools.lint.client.api.JavaParser; import com.android.tools.lint.detector.api.Category; import com.android.tools.lint.detector.api.Detector; import com.android.tools.lint.detector.api.Implementation; import com.android.tools.lint.detector.api.Issue; import com.android.tools.lint.detector.api.JavaContext; import com.android.tools.lint.detector.api.Location; import com.android.tools.lint.detector.api.Scope; import com.android.tools.lint.detector.api.Severity; import com.android.tools.lint.detector.api.Speed; import org.objectweb.asm.Opcodes; import java.util.Collections; import java.util.List; import lombok.ast.AstVisitor; import lombok.ast.ClassDeclaration; import lombok.ast.ForwardingAstVisitor; import lombok.ast.Node; import lombok.ast.TypeReference; /** * Looks for Parcelable classes that are missing a CREATOR field */ public class ParcelDetector extends Detector implements Detector.JavaScanner { /** The main issue discovered by this detector */ public static final Issue ISSUE = Issue.create( "ParcelCreator", //$NON-NLS-1$ "Missing Parcelable `CREATOR` field", "According to the `Parcelable` interface documentation, " + "\"Classes implementing the Parcelable interface must also have a " + "static field called `CREATOR`, which is an object implementing the " + "`Parcelable.Creator` interface.", Category.USABILITY, 3, Severity.ERROR, new Implementation( ParcelDetector.class, Scope.JAVA_FILE_SCOPE)) .addMoreInfo("http://developer.android.com/reference/android/os/Parcelable.html"); /** Constructs a new {@link com.android.tools.lint.checks.ParcelDetector} check */ public ParcelDetector() { } @NonNull @Override public Speed getSpeed() { return Speed.FAST; } // ---- Implements JavaScanner ---- @Nullable @Override public List<Class<? extends Node>> getApplicableNodeTypes() { return Collections.<Class<? extends Node>>singletonList(ClassDeclaration.class); } @Nullable @Override public AstVisitor createJavaVisitor(@NonNull final JavaContext context) { return new ParcelVisitor(context); } private static class ParcelVisitor extends ForwardingAstVisitor { private final JavaContext mContext; public ParcelVisitor(JavaContext context) { mContext = context; } @Override public boolean visitClassDeclaration(ClassDeclaration node) { // Only applies to concrete classes int flags = node.astModifiers().getExplicitModifierFlags(); if ((flags & (Opcodes.ACC_INTERFACE | Opcodes.ACC_ABSTRACT)) != 0) { return true; } if (node.astImplementing() != null) for (TypeReference reference : node.astImplementing()) { String name = reference.astParts().last().astIdentifier().astValue(); if (name.equals("Parcelable")) { JavaParser.ResolvedNode resolved = mContext.resolve(node); if (resolved instanceof ResolvedClass) { ResolvedClass cls = (ResolvedClass) resolved; ResolvedField field = cls.getField("CREATOR", false); if (field == null) { // Make doubly sure that we're really implementing // android.os.Parcelable JavaParser.ResolvedNode r = mContext.resolve(reference); if (r instanceof ResolvedClass) { ResolvedClass parcelable = (ResolvedClass) r; if (!parcelable.isSubclassOf("android.os.Parcelable", false)) { return true; } } Location location = mContext.getLocation(node.astName()); mContext.report(ISSUE, node, location, "This class implements `Parcelable` but does not " + "provide a `CREATOR` field"); } } break; } } return true; } } }