/*
 * 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.PROGUARD_CONFIG;
import static com.android.SdkConstants.PROJECT_PROPERTIES;

import com.android.annotations.NonNull;
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.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 java.io.File;

/**
 * Check which looks for errors in Proguard files.
 */
public class ProguardDetector extends Detector {

    private static final Implementation IMPLEMENTATION = new Implementation(ProguardDetector.class,
            Scope.PROGUARD_SCOPE);

    /** The main issue discovered by this detector */
    public static final Issue WRONG_KEEP = Issue.create(
            "Proguard", //$NON-NLS-1$
            "Using obsolete ProGuard configuration",
            "Using `-keepclasseswithmembernames` in a proguard config file is not " +
            "correct; it can cause some symbols to be renamed which should not be.\n" +
            "Earlier versions of ADT used to create proguard.cfg files with the " +
            "wrong format. Instead of `-keepclasseswithmembernames` use " +
            "`-keepclasseswithmembers`, since the old flags also implies " +
            "\"allow shrinking\" which means symbols only referred to from XML and " +
            "not Java (such as possibly CustomViews) can get deleted.",
            Category.CORRECTNESS,
            8,
            Severity.FATAL,
            IMPLEMENTATION)
            .addMoreInfo(
                    "http://http://code.google.com/p/android/issues/detail?id=16384"); //$NON-NLS-1$

    /** Finds ProGuard files that contain non-project specific configuration
     * locally and suggests replacing it with an include path */
    public static final Issue SPLIT_CONFIG = Issue.create(
            "ProguardSplit", //$NON-NLS-1$
            "Proguard.cfg file contains generic Android rules",

            "Earlier versions of the Android tools bundled a single `proguard.cfg` file " +
            "containing a ProGuard configuration file suitable for Android shrinking and " +
            "obfuscation. However, that version was copied into new projects, which " +
            "means that it does not continue to get updated as we improve the default " +
            "ProGuard rules for Android.\n" +
            "\n" +
            "In the new version of the tools, we have split the ProGuard configuration " +
            "into two halves:\n" +
            "* A simple configuration file containing only project-specific flags, in " +
            "your project\n" +
            "* A generic configuration file containing the recommended set of ProGuard " +
            "options for Android projects. This generic file lives in the SDK install " +
            "directory which means that it gets updated along with the tools.\n" +
            "\n" +
            "In order for this to work, the proguard.config property in the " +
            "`project.properties` file now refers to a path, so you can reference both " +
            "the generic file as well as your own (and any additional files too).\n" +
            "\n" +
            "To migrate your project to the new setup, create a new `proguard-project.txt` file " +
            "in your project containing any project specific ProGuard flags as well as " +
            "any customizations you have made, then update your project.properties file " +
            "to contain:\n" +
            "`proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt`",

            Category.CORRECTNESS,
            3,
            Severity.WARNING,
            IMPLEMENTATION);

    @Override
    public void run(@NonNull Context context) {
        String contents = context.getContents();
        if (contents != null) {
            if (context.isEnabled(WRONG_KEEP)) {
                int index = contents.indexOf(
                        // Old pattern:
                        "-keepclasseswithmembernames class * {\n" + //$NON-NLS-1$
                        "    public <init>(android.");              //$NON-NLS-1$
                if (index != -1) {
                    context.report(WRONG_KEEP,
                            Location.create(context.file, contents, index, index),
                            "Obsolete ProGuard file; use `-keepclasseswithmembers` instead of " +
                            "`-keepclasseswithmembernames`");
                }
            }
            if (context.isEnabled(SPLIT_CONFIG)) {
                int index = contents.indexOf("-keep public class * extends android.app.Activity");
                if (index != -1) {
                    // Only complain if project.properties actually references this file;
                    // no need to bother the users who got a default proguard.cfg file
                    // when they created their projects but haven't actually hooked it up
                    // to shrinking and obfuscation.
                    File propertyFile = new File(context.file.getParentFile(), PROJECT_PROPERTIES);
                    if (!propertyFile.exists()) {
                        return;
                    }
                    String properties = context.getClient().readFile(propertyFile);
                    int i = properties.indexOf(PROGUARD_CONFIG);
                    if (i == -1) {
                        return;
                    }
                    // Make sure the entry isn't just commented out, such as
                    // # To enable ProGuard to shrink and obfuscate your code, uncomment this:
                    // #proguard.config=proguard.cfg
                    for (; i >= 0; i--) {
                        char c = properties.charAt(i);
                        if (c == '#') {
                            return;
                        }
                        if (c == '\n') {
                            break;
                        }
                    }
                    if (properties.contains(PROGUARD_CONFIG)) {
                        context.report(SPLIT_CONFIG,
                            Location.create(context.file, contents, index, index),
                            String.format(
                            "Local ProGuard configuration contains general Android " +
                            "configuration: Inherit these settings instead? " +
                            "Modify `project.properties` to define " +
                            "`proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:%1$s`" +
                            " and then keep only project-specific configuration here",
                            context.file.getName()));
                    }
                }
            }
        }
    }

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

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