All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.android.tools.lint.checks.MergeRootFrameLayoutDetector Maven / Gradle / Ivy

/*
 * 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.checks;

import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_BACKGROUND;
import static com.android.SdkConstants.ATTR_FOREGROUND;
import static com.android.SdkConstants.ATTR_LAYOUT;
import static com.android.SdkConstants.ATTR_LAYOUT_GRAVITY;
import static com.android.SdkConstants.DOT_JAVA;
import static com.android.SdkConstants.FRAME_LAYOUT;
import static com.android.SdkConstants.LAYOUT_RESOURCE_PREFIX;
import static com.android.SdkConstants.R_LAYOUT_RESOURCE_PREFIX;
import static com.android.SdkConstants.VIEW_INCLUDE;

import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
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.LayoutDetector;
import com.android.tools.lint.detector.api.LintUtils;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.Location.Handle;
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 com.android.tools.lint.detector.api.XmlContext;
import com.android.utils.Pair;

import org.w3c.dom.Element;
import org.w3c.dom.Node;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import lombok.ast.AstVisitor;
import lombok.ast.Expression;
import lombok.ast.MethodInvocation;
import lombok.ast.Select;
import lombok.ast.StrictListAccessor;

/**
 * Checks whether a root FrameLayout can be replaced with a {@code } tag.
 */
public class MergeRootFrameLayoutDetector extends LayoutDetector implements Detector.JavaScanner {
    /**
     * Set of layouts that we want to enable the warning for. We only warn for
     * {@code }'s that are the root of a layout included from
     * another layout, or directly referenced via a {@code setContentView} call.
     */
    private Set mWhitelistedLayouts;

    /**
     * Set of pending [layout, location] pairs where the given layout is a
     * FrameLayout that perhaps should be replaced by a {@code } tag (if
     * the layout is included or set as the content view. This must be processed
     * after the whole project has been scanned since the set of includes etc
     * can be encountered after the included layout.
     */
    private List> mPending;

    /** The main issue discovered by this detector */
    public static final Issue ISSUE = Issue.create(
            "MergeRootFrame", //$NON-NLS-1$
            "FrameLayout can be replaced with `` tag",

            "If a `` is the root of a layout and does not provide background " +
            "or padding etc, it can often be replaced with a `` tag which is slightly " +
            "more efficient. Note that this depends on context, so make sure you understand " +
            "how the `` tag works before proceeding.",
            Category.PERFORMANCE,
            4,
            Severity.WARNING,
            new Implementation(
                    MergeRootFrameLayoutDetector.class,
                    EnumSet.of(Scope.ALL_RESOURCE_FILES, Scope.JAVA_FILE)))
            .addMoreInfo(
            "http://android-developers.blogspot.com/2009/03/android-layout-tricks-3-optimize-by.html"); //$NON-NLS-1$

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

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

    @Override
    public boolean appliesTo(@NonNull Context context, @NonNull File file) {
        return LintUtils.isXmlFile(file) || LintUtils.endsWith(file.getName(), DOT_JAVA);
    }

    @Override
    public void afterCheckProject(@NonNull Context context) {
        if (mPending != null && mWhitelistedLayouts != null) {
            // Process all the root FrameLayouts that are eligible, and generate
            // suggestions for  replacements for any layouts that are included
            // from other layouts
            for (Pair pair : mPending) {
                String layout = pair.getFirst();
                if (mWhitelistedLayouts.contains(layout)) {
                    Handle handle = pair.getSecond();

                    Object clientData = handle.getClientData();
                    if (clientData instanceof Node) {
                        if (context.getDriver().isSuppressed(null, ISSUE, (Node) clientData)) {
                            return;
                        }
                    }

                    Location location = handle.resolve();
                    context.report(ISSUE, location,
                            "This `` can be replaced with a `` tag");
                }
            }
        }
    }

    // Implements XmlScanner

    @Override
    public Collection getApplicableElements() {
        return Arrays.asList(VIEW_INCLUDE, FRAME_LAYOUT);
    }

    @Override
    public void visitElement(@NonNull XmlContext context, @NonNull Element element) {
        String tag = element.getTagName();
        if (tag.equals(VIEW_INCLUDE)) {
            String layout = element.getAttribute(ATTR_LAYOUT); // NOTE: Not in android: namespace
            if (layout.startsWith(LAYOUT_RESOURCE_PREFIX)) { // Ignore @android:layout/ layouts
                layout = layout.substring(LAYOUT_RESOURCE_PREFIX.length());
                whiteListLayout(layout);
            }
        } else {
            assert tag.equals(FRAME_LAYOUT);
            if (LintUtils.isRootElement(element) &&
                ((isWidthFillParent(element) && isHeightFillParent(element)) ||
                        !element.hasAttributeNS(ANDROID_URI, ATTR_LAYOUT_GRAVITY))
                    && !element.hasAttributeNS(ANDROID_URI, ATTR_BACKGROUND)
                    && !element.hasAttributeNS(ANDROID_URI, ATTR_FOREGROUND)
                    && !hasPadding(element)) {
                String layout = LintUtils.getLayoutName(context.file);
                Handle handle = context.createLocationHandle(element);
                handle.setClientData(element);

                if (!context.getProject().getReportIssues()) {
                    // If this is a library project not being analyzed, ignore it
                    return;
                }

                if (mPending == null) {
                    mPending = new ArrayList>();
                }
                mPending.add(Pair.of(layout, handle));
            }
        }
    }

    private void whiteListLayout(String layout) {
        if (mWhitelistedLayouts == null) {
            mWhitelistedLayouts = new HashSet();
        }
        mWhitelistedLayouts.add(layout);
    }

    // Implements JavaScanner

    @Override
    public List getApplicableMethodNames() {
        return Collections.singletonList("setContentView"); //$NON-NLS-1$
    }

    @Override
    public void visitMethod(
            @NonNull JavaContext context,
            @Nullable AstVisitor visitor,
            @NonNull MethodInvocation node) {
        StrictListAccessor argumentList = node.astArguments();
        if (argumentList != null && argumentList.size() == 1) {
            Expression argument = argumentList.first();
            if (argument instanceof Select) {
                String expression = argument.toString();
                if (expression.startsWith(R_LAYOUT_RESOURCE_PREFIX)) {
                    whiteListLayout(expression.substring(R_LAYOUT_RESOURCE_PREFIX.length()));
                }
            }
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy