1d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye/*
2d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * Copyright (C) 2012 The Android Open Source Project
3d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye *
4d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * Licensed under the Eclipse Public License, Version 1.0 (the "License");
5d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * you may not use this file except in compliance with the License.
6d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * You may obtain a copy of the License at
7d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye *
8d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye *      http://www.eclipse.org/org/documents/epl-v10.php
9d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye *
10d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * Unless required by applicable law or agreed to in writing, software
11d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * distributed under the License is distributed on an "AS IS" BASIS,
12d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * See the License for the specific language governing permissions and
14d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * limitations under the License.
15d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye */
16d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyepackage com.android.ide.eclipse.adt.internal.lint;
17d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye
1812d4581faa6438941e65a9dc83213be34c6ca970Tor Norbyeimport static com.android.SdkConstants.DOT_CLASS;
1912d4581faa6438941e65a9dc83213be34c6ca970Tor Norbyeimport static com.android.SdkConstants.DOT_JAVA;
2012d4581faa6438941e65a9dc83213be34c6ca970Tor Norbyeimport static com.android.SdkConstants.EXT_JAVA;
21d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye
22d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport com.android.annotations.NonNull;
238dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbyeimport com.android.annotations.Nullable;
24d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport com.android.ide.eclipse.adt.AdtPlugin;
25d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport com.android.ide.eclipse.adt.AdtUtils;
268dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbyeimport com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
278dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbyeimport com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor;
288dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbyeimport com.android.ide.eclipse.adt.internal.resources.manager.GlobalProjectMonitor.IFileListener;
298dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbyeimport com.android.ide.eclipse.adt.internal.resources.manager.ResourceManager;
30d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye
31d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport org.eclipse.core.resources.IFile;
328dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbyeimport org.eclipse.core.resources.IMarkerDelta;
33d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport org.eclipse.core.resources.IResource;
34d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport org.eclipse.core.resources.IResourceDelta;
35d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport org.eclipse.swt.widgets.Display;
36d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye
37d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport java.util.ArrayList;
388dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbyeimport java.util.Collections;
39d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyeimport java.util.List;
40d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye
41d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye/**
42d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * Delta processor for Java files, which runs single-file lints if it finds that
43d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye * the currently active file has been updated.
44d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye */
45d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbyepublic class LintDeltaProcessor implements Runnable {
46d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    private List<IResource> mFiles;
47d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    private IFile mActiveFile;
48d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye
49d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    private LintDeltaProcessor() {
508dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        // Get the active editor file, if any
518dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        Display display = AdtPlugin.getDisplay();
528dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        if (display == null || display.isDisposed()) {
538dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye            return;
548dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        }
558dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        if (display.getThread() != Thread.currentThread()) {
568dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye            display.syncExec(this);
578dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        } else {
588dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye            run();
598dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        }
60d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    }
61d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye
62d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    /**
63d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye     * Creates a new {@link LintDeltaProcessor}
64d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye     *
65d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye     * @return a visitor
66d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye     */
67d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    @NonNull
68d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    public static LintDeltaProcessor create() {
69d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye        return new LintDeltaProcessor();
70d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    }
71d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye
72d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    /**
73d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye     * Process the given delta: update lint on any Java source and class files found.
74d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye     *
75d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye     * @param delta the delta describing recently changed files
76d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye     */
77d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    public void process(@NonNull IResourceDelta delta)  {
78d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye        if (mActiveFile == null || !mActiveFile.getName().endsWith(DOT_JAVA)) {
79d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            return;
80d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye        }
81d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye
82d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye        mFiles = new ArrayList<IResource>();
83d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye        gatherFiles(delta);
84d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye
85d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye        if (!mFiles.isEmpty()) {
86d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            EclipseLintRunner.startLint(mFiles, mActiveFile, null,
87d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye                    false /*fatalOnly*/, false /*show*/);
88d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye        }
89d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    }
90d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye
91d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    /**
928dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye     * Process edits in the given file: update lint on the Java source provided
938dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye     * it's the active file.
948dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye     *
958dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye     * @param file the file that was changed
968dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye     */
978dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye    public void process(@NonNull IFile file)  {
988dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        if (mActiveFile == null || !mActiveFile.getName().endsWith(DOT_JAVA)) {
998dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye            return;
1008dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        }
1018dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye
1028dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        if (file.equals(mActiveFile)) {
1038dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye            mFiles = Collections.<IResource>singletonList(file);
1048dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye            EclipseLintRunner.startLint(mFiles, mActiveFile, null,
1058dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye                    false /*fatalOnly*/, false /*show*/);
1068dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        }
1078dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye    }
1088dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye
1098dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye    /**
110d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye     * Collect .java and .class files to be run in lint. Only collects files
111d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye     * that match the active editor.
112d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye     */
113d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    private void gatherFiles(@NonNull IResourceDelta delta) {
114d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye        IResource resource = delta.getResource();
115d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye        String name = resource.getName();
116d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye        if (name.endsWith(DOT_JAVA)) {
117d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            if (resource.equals(mActiveFile)) {
118d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye                mFiles.add(resource);
119d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            }
120d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye        } else if (name.endsWith(DOT_CLASS)) {
121d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            // Make sure this class corresponds to the .java file, meaning it has
122d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            // the same basename, or that it is an inner class of a class that
123d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            // matches the same basename. (We could potentially make sure the package
124d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            // names match too, but it's unlikely that the class names match without a
125d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            // package match, and there's no harm in including some extra classes here,
126d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            // since lint will resolve full paths and the resource markers won't go
127d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            // to the wrong place, we simply end up analyzing some extra files.)
128d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            String className = mActiveFile.getName();
129d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            if (name.regionMatches(0, className, 0, className.length() - DOT_JAVA.length())) {
130d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye                if (name.length() == className.length() - DOT_JAVA.length() + DOT_CLASS.length()
131d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye                        || name.charAt(className.length() - DOT_JAVA.length()) == '$') {
132d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye                    mFiles.add(resource);
133d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye                }
134d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            }
135d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye        } else {
136d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            IResourceDelta[] children = delta.getAffectedChildren();
137d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            if (children != null && children.length > 0) {
138d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye                for (IResourceDelta d : children) {
139d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye                    gatherFiles(d);
140d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye                }
141d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye            }
142d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye        }
143d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    }
144d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye
145d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    @Override
146d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    public void run() {
147d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye        // Get the active file: this must be run on the GUI thread
148d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye        mActiveFile = AdtUtils.getActiveFile();
149d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye    }
1508dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye
1518dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye    /**
1528dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye     * Start listening to the resource monitor
1538dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye     *
1548dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye     * @param resourceMonitor the resource monitor
1558dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye     */
1568dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye    public static void startListening(@NonNull GlobalProjectMonitor resourceMonitor) {
1578dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        // Add a file listener which finds out when files have changed. This is listening
1588dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        // specifically for saves of Java files, in order to run incremental lint on them.
1598dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        // Note that the {@link PostCompilerBuilder} already handles incremental lint files
1608dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        // on Java files - and runs it for both the .java and .class files.
1618dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        //
1628dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        // However, if Project > Build Automatically is turned off, then the PostCompilerBuilder
1638dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        // isn't run after a save. THAT's what the below is for: it will run and *only*
1648dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        // run lint incrementally if build automatically is off.
1658dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        assert sListener == null; // Should only be called once on plugin activation
1668dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        sListener = new IFileListener() {
1678dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye            @Override
1688dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye            public void fileChanged(@NonNull IFile file,
1698dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye                    @NonNull IMarkerDelta[] markerDeltas,
1708dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye                    int kind, @Nullable String extension, int flags) {
1718dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye                if (flags == IResourceDelta.MARKERS) {
1728dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye                    // ONLY the markers changed. Ignore these since they happen
1738dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye                    // when we add markers for lint errors found in the current file,
1748dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye                    // which would cause us to repeatedly enter this method over and over
1758dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye                    // again.
1768dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye                    return;
1778dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye                }
1788dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye                if (EXT_JAVA.equals(extension)
1798dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye                        && !ResourceManager.isAutoBuilding()
1808dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye                        && AdtPrefs.getPrefs().isLintOnSave()) {
1818dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye                    LintDeltaProcessor.create().process(file);
1828dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye                }
1838dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye            }
1848dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        };
1858dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        resourceMonitor.addFileListener(sListener, IResourceDelta.ADDED | IResourceDelta.CHANGED);
1868dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye    }
1878dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye
1888dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye    /**
1898dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye     * Stop listening to the resource monitor
1908dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye     *
1918dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye     * @param resourceMonitor the resource monitor
1928dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye     */
1938dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye    public static void stopListening(@NonNull GlobalProjectMonitor resourceMonitor) {
1948dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        assert sListener != null;
1958dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        resourceMonitor.removeFileListener(sListener);
1968dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye        sListener = null;
1978dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye    }
1988dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye
1998dcd0726088eee54368e032e7dc6b31a450b4ee1Tor Norbye    private static IFileListener sListener;
200d02dad911cea4f854e32cc99e10cdd31ab89a795Tor Norbye}
201