aboutsummaryrefslogtreecommitdiffstats
path: root/lint/libs/lint_checks/src/com/android/tools/lint/checks/ViewConstructorDetector.java
blob: 72aaa77cd662c7783dc08646992006307f2a362e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/*
 * Copyright (C) 2012 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.detector.api.LintConstants.CONSTRUCTOR_NAME;

import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.ClassContext;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Issue;
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 org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;

import java.io.File;
import java.util.List;

/**
 * Looks for custom views that do not define the view constructors needed by UI builders
 */
public class ViewConstructorDetector extends Detector implements Detector.ClassScanner {
    private static final String SIG1 =
            "(Landroid/content/Context;)V"; //$NON-NLS-1$
    private static final String SIG2 =
            "(Landroid/content/Context;Landroid/util/AttributeSet;)V"; //$NON-NLS-1$
    private static final String SIG3 =
            "(Landroid/content/Context;Landroid/util/AttributeSet;I)V"; //$NON-NLS-1$

    /** The main issue discovered by this detector */
    public static final Issue ISSUE = Issue.create(
            "ViewConstructor", //$NON-NLS-1$
            "Checks that custom views define the expected constructors",

            "Some layout tools (such as the Android layout editor for Eclipse) needs to " +
            "find a constructor with one of the following signatures:\n" +
            "* View(Context context)\n" +
            "* View(Context context, AttributeSet attrs)\n" +
            "* View(Context context, AttributeSet attrs, int defStyle)\n" +
            "\n" +
            "If your custom view needs to perform initialization which does not apply when " +
            "used in a layout editor, you can surround the given code with a check to " +
            "see if View#isInEditMode() is false, since that method will return false " +
            "at runtime but true within a user interface editor.",

            Category.USABILITY,
            3,
            Severity.WARNING,
            ViewConstructorDetector.class,
            Scope.CLASS_FILE_SCOPE);

    /** Constructs a new {@link ViewConstructorDetector} check */
    public ViewConstructorDetector() {
    }

    @Override
    public boolean appliesTo(Context context, File file) {
        return true;
    }

    @Override
    public Speed getSpeed() {
        return Speed.FAST;
    }

    // ---- Implements ClassScanner ----

    @Override
    public void checkClass(ClassContext context, ClassNode classNode) {
        if (classNode.name.indexOf('$') != -1
            && (classNode.access & Opcodes.ACC_STATIC) == 0) {
            // Ignore inner classes that aren't static: we can't create these
            // anyway since we'd need the outer instance
            return;
        }

        if (isViewClass(context, classNode)) {
            checkConstructors(context, classNode);
        }
    }

    private static boolean isViewClass(ClassContext context, ClassNode node) {
        String superName = node.superName;
        while (superName != null) {
            if (superName.equals("android/view/View")                //$NON-NLS-1$
                    || superName.equals("android/view/ViewGroup")    //$NON-NLS-1$
                    || superName.startsWith("android/widget/")       //$NON-NLS-1$
                    && !((superName.endsWith("Adapter")              //$NON-NLS-1$
                            || superName.endsWith("Controller")      //$NON-NLS-1$
                            || superName.endsWith("Service")         //$NON-NLS-1$
                            || superName.endsWith("Provider")        //$NON-NLS-1$
                            || superName.endsWith("Filter")))) {     //$NON-NLS-1$
                return true;
            }

            superName = context.getDriver().getSuperClass(superName);
        }

        return false;
    }

    private void checkConstructors(ClassContext context, ClassNode classNode) {
        // Look through constructors
        @SuppressWarnings("rawtypes")
        List methods = classNode.methods;
        for (Object methodObject : methods) {
            MethodNode method = (MethodNode) methodObject;
            if (method.name.equals(CONSTRUCTOR_NAME)) {
                String desc = method.desc;
                if (desc.equals(SIG1) || desc.equals(SIG2) || desc.equals(SIG3)) {
                    return;
                }
            }
        }

        // If we get this far, none of the expected constructors were found.

        // Use location of one of the constructors?
        String message = String.format(
                "Custom view %1$s is missing constructor used by tools: " +
                "(Context) or (Context,AttributeSet) or (Context,AttributeSet,int)",
                classNode.name);
        File sourceFile = context.getSourceFile();
        Location location = Location.create(sourceFile != null
                ? sourceFile : context.file);
        context.report(ISSUE, location, message, null /*data*/);
    }
}